<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>Luke Hearon</title>
<link>https://www.lukehearon.com/blog/</link>
<atom:link href="https://www.lukehearon.com/blog/index.xml" rel="self" type="application/rss+xml"/>
<description>Entomology, data science, ramblings</description>
<generator>quarto-1.8.27</generator>
<lastBuildDate>Mon, 30 Mar 2026 04:00:00 GMT</lastBuildDate>
<item>
  <title>A walkthrough for conducting research with buzzdetect</title>
  <link>https://www.lukehearon.com/blog/2026/buzzdetect-walkthrough/</link>
  <description><![CDATA[ 





<style>
  .quarto-title-block .quarto-title-banner {
    filter: brightness(1.1);
    background-position: bottom;
  }
</style>
<section id="overview" class="level2">
<h2 class="anchored" data-anchor-id="overview">Overview</h2>
<p>This post is a walkthrough for conducting a bioacoustic analysis using <a href="https://github.com/OSU-Bee-Lab/buzzdetect">buzzdetect</a>. The following steps will take you from raw audio files to statistical results; by the end of this walkthrough, you should have a sense of how to shape, analyze, interpret, and visualize buzzdetect results.<span class="tippy html-widget html-fill-item" height="338px" id="htmlwidget-9ba9629c2057d048a767" width="100%"></span> <script type="application/json" data-for="htmlwidget-9ba9629c2057d048a767">{"x":{"opts":{"animation":"scale","duration":70,"allowHTML":true,"interactive":true,"content":"Because buzzdetect is under active development, this guide will not address running buzzdetect itself. This is covered in <a href=\"https://buzzdetect.readthedocs.io/en/latest/\">the buzzdetect documentation<\/a>."},"text":"<span style='color: var(--clr-tooltip);'><sup>※<\/sup><\/span>"},"evals":[],"jsHooks":[]}</script> I’ll share R code, figures, and a few tidbits of hard-earned wisdom to help aspiring bioacousticians plan their experiments and analyses carefully. If you’re using buzzdetect in your research or if you’re just interested in what a bioacoustic workflow looks like, read on!</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Our current model, <code>model_general_v3</code> seems to perform well on honey bees and bumble bees. We have, however, observed that the model can be systematically deaf to certain types of buzzes. For example, a recording of <em>Melissodes</em> on sunflower captured easily audible buzzing, but did not produce activations from this model.</p>
<p>If you want to using <code>model_general_v3</code> to study other pollinators, I would be happy to help you explore the feasibility!</p>
</div>
</div>
</section>
<section id="data" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="data">The Dataset</h2>
<p>We’ll be using the same dataset that was used in our original buzzdetect publication.</p>

<div class="no-row-height column-margin column-container"><div class="margin-aside">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><a href="recorder.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="One of our intrepid recorders deployed above a pumpkin flower."><img src="https://www.lukehearon.com/blog/2026/buzzdetect-walkthrough/recorder.png" class="img-fluid figure-img" alt="One of our intrepid recorders deployed above a pumpkin flower."></a></p>
<figcaption>One of our intrepid recorders deployed above a pumpkin flower.</figcaption>
</figure>
</div>
</div></div><section id="audio-files" class="level3">
<h3 class="anchored" data-anchor-id="audio-files">Audio files</h3>
<p>The recordings were taken from five types of flower: pumpkin, chicory, watermelon, mustard, and soybean. For each flower, eight recorders were affixed to plastic step-in fence posts, protected with a 3D printed raincover, and deployed as close to the flower as possible. The resulting audio was trimmed to one full day of recording, midnight-to-midnight. Recordings from the same flower were all taken at the same time and location, but different flowers were recorded on different days.<span class="tippy html-widget html-fill-item" height="338px" id="htmlwidget-1ed6d40ec83a036905bd" width="100%"></span> <script type="application/json" data-for="htmlwidget-1ed6d40ec83a036905bd">{"x":{"opts":{"animation":"scale","duration":70,"allowHTML":true,"interactive":true,"content":"We'll run stats on the buzzdetect results, but this dataset is only demonstrative. Our site-level and day-level replication is n=1."},"text":"<span style='color: var(--clr-tooltip);'><sup>※<\/sup><\/span>"},"evals":[],"jsHooks":[]}</script></p>
<p>We used <a href="https://www.sony.com/electronics/support/digital-voice-recorders-icd-series/icd-px370">Sony ICD-PX370</a> MP3 recorders for monitoring. Audio was recorded at 44.1 kHz and a highly compressed 48 kbps.<span class="tippy html-widget html-fill-item" height="338px" id="htmlwidget-7ac612f7f366d483763e" width="100%"></span> <script type="application/json" data-for="htmlwidget-7ac612f7f366d483763e">{"x":{"opts":{"animation":"scale","duration":70,"allowHTML":true,"interactive":true,"content":"This is the audio format our models were trained on, but detection doesn't seem to be affected much by different formats. See the <a href=https://zenodo.org/records/17857856/preview/buzzdetect.zip?include_deleted=0#tree_item21>supplement - audio formats<\/a> code in the repository for an analysis."},"text":"<span style='color: var(--clr-tooltip);'><sup>※<\/sup><\/span>"},"evals":[],"jsHooks":[]}</script> Each recording is named according to the start time of the audio, Eastern Standard Time in the format YYMMDD_HHMM.<span class="tippy html-widget html-fill-item" height="338px" id="htmlwidget-603e3c34232b5896d98e" width="100%"></span> <script type="application/json" data-for="htmlwidget-603e3c34232b5896d98e">{"x":{"opts":{"animation":"scale","duration":70,"allowHTML":true,"interactive":true,"content":"Tragically, the format used by the Sony recorders is not <a href=https://www.iso.org/iso-8601-date-and-time-format.html>ISO 8601<\/a> compliant. You should stick to this standard whenever possible! Date-time formatting WILL cause you heartache eventually."},"text":"<span style='color: var(--clr-tooltip);'><sup>※<\/sup><\/span>"},"evals":[],"jsHooks":[]}</script></p>
<p>The audio files are organized into folders according to the following structure: <code>[flower]/[recorder_id]/[audio file]</code>. For example:</p>
<pre class="text"><code>full_recordings
├── chicory
│   ├── 1_104
│   │   └── 250704_0000.mp3
│   ├── 1_109
│   │   └── 250704_0000.mp3
│   └── ...
├── mustard
│   ├── 1_103
│   │   └── 240904_0000.mp3
│   ├── 1_29
│   │   └── 240904_0000.mp3
│   └── ...
└── ...</code></pre>
</section>
<section id="results-files" class="level3">
<h3 class="anchored" data-anchor-id="results-files">Results files</h3>
<p>Each audio file buzzdetect analyzes produces one output results file. The file names will be identical, with the addition of a “_buzzdetect” suffix to the results file. buzzdetect also clones the directory structure of the input files, so for the audio in this dataset, the results look like:</p>
<pre class="text"><code>full_results
├── chicory
│   ├── 1_104
│   │   └── 250704_0000_buzzdetect.csv
│   ├── 1_109
│   │   └── 250704_0000_buzzdetect.csv
│   └── ...
├── mustard
│   ├── 1_103
│   │   └── 240904_0000_buzzdetect.csv
│   ├── 1_29
│   │   └── 240904_0000_buzzdetect.csv
│   └── ...
└── ...</code></pre>
<p>A results file is a CSV. Every row in the CSV is one frame of audio. The “start” column records the start time of each frame. There is no “end” column, since the end is always one framelength after the start. For model_general_v3, every frame is 0.96 seconds long, so a frame with a start of 10.00s ends at 10.96s. The remaining columns in the results hold the activation values for the model’s neurons. These are all prefixed with “activation_”.<span class="tippy html-widget html-fill-item" height="338px" id="htmlwidget-f9cab0b08dfbe711c832" width="100%"></span> <script type="application/json" data-for="htmlwidget-f9cab0b08dfbe711c832">{"x":{"opts":{"animation":"scale","duration":70,"allowHTML":true,"interactive":true,"content":"If you chose to output detections, you will only have a detections_ins_buzz column which holds a binary detection for each frame."},"text":"<span style='color: var(--clr-tooltip);'><sup>※<\/sup><\/span>"},"evals":[],"jsHooks":[]}</script> We’re usually only interested in the “activation_ins_buzz” columns, and buzzdetect allows you to drop certain columns during analysis, so you might not see all neurons in your results files. Non-buzz neurons only exist to improve model training, though it may be possible to use them to detect events relevant to your study such as rainfall and noise pollution. Anecdotally, the model_general_v3 “ambient_rain” neuron seems to detect medium and heavy rainfall decently well at a threshold of -1.</p>
</section>
<section id="download" class="level3">
<h3 class="anchored" data-anchor-id="download">Downloading the dataset</h3>
<section id="download-audio" class="level4">
<h4 class="anchored" data-anchor-id="download-audio">From the beginning: audio data</h4>
<p><a href="https://zenodo.org/records/17857856"><strong>Download - ~20 GB</strong></a></p>
<p>If you would like to truly start from square one, you can download the Zenodo repository. The raw audio files are in <code>data/raw/full_recordings/</code>, but you have to download the whole repository in one go. Use the extracted folder as your working directory (there’s already an R project file in the repository, buzzdetect.Rproj).</p>
<p>The repository also contains numerous supplementary code files for reproducing the figures and statistical analyses that appear in the manuscript. We’ll be going through similar steps here, but feel free to poke around and steal code at will.</p>
<p>Proceed to Step One: Analyze.</p>
</section>
<section id="download-results" class="level4">
<h4 class="anchored" data-anchor-id="download-results">Skipping ahead: buzzdetect results</h4>
<p><a href="../../../datasets/five_flowers.zip"><strong>Download - ~22 MB</strong></a></p>
<p>This is a trimmed-down version of the results from a run of model_general_v3 on the raw audio with a <span class="tippy html-widget html-fill-item" height="338px" id="htmlwidget-293f2800a360717911a1" width="100%"></span> <script type="application/json" data-for="htmlwidget-293f2800a360717911a1">{"x":{"opts":{"animation":"scale","duration":70,"allowHTML":true,"interactive":true,"content":"The distance between the start of one frame and the next, expressed as a proportion. Framehop of 1 produces contiguous frames."},"text":"<span style='background-color: var(--clr-texttip)'>framehop<\/span>"},"evals":[],"jsHooks":[]}</script> of 1. These are functionally identical the the full results appearing in the Zenodo repository at <code>data/raw/full_results</code>. A few changes have been made compared to the repository:</p>
<ul>
<li><p>The “ins_buzz” column has been renamed to “activation_ins_buzz” to correspond to current buzzdetect and buzzr nomenclature</p></li>
<li><p>Neurons have been trimmed to ins_buzz, ambient_rain, ins_trill, and mech_plane.</p></li>
<li><p>Activations have been rounded to the tenths place.</p></li>
</ul>
<p>Extract this folder to your working environment. You’ll see it has two folders, <code>full_results/</code> and <code>snip_results</code>. We want the first one for this walkthrough.<span class="tippy html-widget html-fill-item" height="338px" id="htmlwidget-95b3782cf25ed4984f39" width="100%"></span> <script type="application/json" data-for="htmlwidget-95b3782cf25ed4984f39">{"x":{"opts":{"animation":"scale","duration":70,"allowHTML":true,"interactive":true,"content":"The snip_results folder corresponds to the 5-minute bins that were manually annotated for model testing. Those annotations are also stored in this dataset as annotations.csv"},"text":"<span style='color: var(--clr-tooltip);'><sup>※<\/sup><\/span>"},"evals":[],"jsHooks":[]}</script> The following steps will assume the results from the full_results folder are stored in <code>data/intermediate/full_results</code>. E.g., <code>data/intermediate/full_results/chicory/1_37/250704_0000_buzzdetect.csv</code>.</p>
<p>Proceed to Step Two: Shape.</p>
</section>
</section>
</section>
<section id="analyze" class="level2">
<h2 class="anchored" data-anchor-id="analyze">Step One: Analyze with buzzdetect</h2>
<p>Because buzzdetect is in active development, this walkthrough won’t cover buzzdetect analysis in-depth. Hop over to the <a href="https://buzzdetect.readthedocs.io/en/latest/gui/">buzzdetect documentation</a> for instructions on processing audio with buzzdetect.</p>
<p>Choose the full_recordings directory as your input folder; output your results to <code>data/intermediate/full_results</code>.<span class="tippy html-widget html-fill-item" height="338px" id="htmlwidget-8b8bfb674bfa1f2931bc" width="100%"></span> <script type="application/json" data-for="htmlwidget-8b8bfb674bfa1f2931bc">{"x":{"opts":{"animation":"scale","duration":70,"allowHTML":true,"interactive":true,"content":"In the repository, we stored the buzzdetect results stored as \"raw\" data so that we could automate our analyses with a MAKE file. But the true raw data is the audio, so for this walkthrough we'll output to the intermediate data folder."},"text":"<span style='color: var(--clr-tooltip);'><sup>※<\/sup><\/span>"},"evals":[],"jsHooks":[]}</script> Set the output mode to activations and choose at least the “ins_buzz” neuron for output.</p>
<p>This walkthrough was made using results from model_general_v3. If you use a different model, the results might be a little different.</p>
</section>
<section id="shape" class="level2">
<h2 class="anchored" data-anchor-id="shape">Step Two: Shape with buzzr</h2>
<p>You should now have your buzzdetect results from the five flowers stored in <code>data/intermediate/full_results</code>.</p>
<section id="loading-data" class="level3">
<h3 class="anchored" data-anchor-id="loading-data">Loading data</h3>
<p>While the cloned output files respect the user’s structure, dozens of separate CSV files are a headache to read into R, right? Wrong! buzzdetect has a companion package, <a href="https://github.com/OSU-Bee-Lab/buzzr">buzzr</a>, that will handle reading and joining results for us. buzzr is doing a lot under the hood, but I’ll leave the detailed explanation <a href="https://osu-bee-lab.github.io/buzzr/articles/buzzr.html">to the buzzr walkthrough</a>.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(buzzr) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># for shaping buzzdetect results</span></span>
<span id="cb3-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(dplyr)</span>
<span id="cb3-3">dir_data <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'data/intermediate/full_results'</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># your results should be here relative to the working directory</span></span></code></pre></div></div>
</div>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>Even though I loaded buzzr, I’m going to use the <a href="https://rdrr.io/r/base/ns-dblcolon.html">double colon operator</a> <code>::</code> in these code blurbs. That way, it’s clear which functions are from buzzr.</p>
</div>
</div>
<p>Reading in our data is a single buzzr call:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-4" style="background: #f1f3f5;"><pre class="sourceCode r code-annotation-code code-with-copy code-annotated"><code class="sourceCode r"><span id="annotated-cell-4-1">results <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> buzzr<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">bin_directory</span>(</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="1" onclick="event.preventDefault();">1</a><span id="annotated-cell-4-2" class="code-annotation-target">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">dir_results=</span>dir_data,</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="2" onclick="event.preventDefault();">2</a><span id="annotated-cell-4-3" class="code-annotation-target">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">thresholds =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ins_buzz=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.2</span>),</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="3" onclick="event.preventDefault();">3</a><span id="annotated-cell-4-4" class="code-annotation-target">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">dir_nesting =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'flower'</span>,<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'recorder'</span>),</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="4" onclick="event.preventDefault();">4</a><span id="annotated-cell-4-5" class="code-annotation-target">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">posix_formats =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'%y%m%d_%H%M'</span>,</span>
<span id="annotated-cell-4-6">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">tz =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'America/New_York'</span>,</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="5" onclick="event.preventDefault();">5</a><span id="annotated-cell-4-7" class="code-annotation-target">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">binwidth =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>,</span>
<a class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="6" onclick="event.preventDefault();">6</a><span id="annotated-cell-4-8" class="code-annotation-target">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">calculate_rate =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span></span>
<span id="annotated-cell-4-9">)</span>
<span id="annotated-cell-4-10"></span>
<span id="annotated-cell-4-11"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">head</span>(results)</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-grid">
<dt data-target-cell="annotated-cell-4" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="2" data-code-annotation="1">Point to the data directory</span>
</dd>
<dt data-target-cell="annotated-cell-4" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="3" data-code-annotation="2">Set the threshold to call a buzz at -1.2; we consider a buzz present if the neuron activation is above -1.2. This threshold should be 28% sensitive with a 0.3% false positive rate.</span>
</dd>
<dt data-target-cell="annotated-cell-4" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="4" data-code-annotation="3">Tell buzzr how to interpret our directory structure. We have a folder named with the type of flower, then a folder named with the recorder ID. Thus: <code>c('flower', 'recorder')</code></span>
</dd>
<dt data-target-cell="annotated-cell-4" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="5,6" data-code-annotation="4">Give the information needed to interpret date-times from the file names. We need both the POSIX format of the timestamp (see <code>help("format.POSIXct")</code>) and the time zone that these timestamps reflect. Our recorders use HHMMDD_YYMM.</span>
</dd>
<dt data-target-cell="annotated-cell-4" data-target-annotation="5">5</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="7" data-code-annotation="5">Set the width of the bins to 5 minutes. This is a starting point, but different applications need different bin widths.</span>
</dd>
<dt data-target-cell="annotated-cell-4" data-target-annotation="6">6</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="8" data-code-annotation="6">Let buzzr calculate the detection rates for us. This will produce a “detectionrate_” column corresponding to each “detections_” column. It’s simply the detections divided by the total frames.</span>
</dd>
</dl>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>    flower recorder        bin_datetime detections_ins_buzz frames
    &lt;char&gt;   &lt;char&gt;              &lt;POSc&gt;               &lt;int&gt;  &lt;num&gt;
1: chicory    1_104 2025-07-04 00:00:00                   0    313
2: chicory    1_104 2025-07-04 00:05:00                   1    312
3: chicory    1_104 2025-07-04 00:10:00                   0    313
4: chicory    1_104 2025-07-04 00:15:00                   0    312
5: chicory    1_104 2025-07-04 00:20:00                   3    313
6: chicory    1_104 2025-07-04 00:25:00                   0    312
   detectionrate_ins_buzz
                    &lt;num&gt;
1:            0.000000000
2:            0.003205128
3:            0.000000000
4:            0.000000000
5:            0.009584665
6:            0.000000000</code></pre>
</div>
</div>
</section>
</section>
<section id="step-three-plot" class="level2">
<h2 class="anchored" data-anchor-id="step-three-plot">Step Three: Plot</h2>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(ggplot2)</span></code></pre></div></div>
</div>
<p>Let’s explore these data a little bit to motivate our statistics and get a sense of what we can do with bioacoustic results. We’ll do our plotting with ggplot2.</p>
<section id="activity-curves" class="level3">
<h3 class="anchored" data-anchor-id="activity-curves">Activity curves</h3>
<p>First up, my favorite visualization: the activity curve. This is the trend of pollinator activity across the course of the day for each flower. This isn’t a common visualization; usually researchers are interested in point estimates such as the total number of bees. But activity curves flex the immense temporal resolution of bioacoustics</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb6-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(</span>
<span id="cb6-2">  results,</span>
<span id="cb6-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(</span>
<span id="cb6-4">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> bin_datetime,</span>
<span id="cb6-5">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> detectionrate_ins_buzz,</span>
<span id="cb6-6">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> flower</span>
<span id="cb6-7">  )</span>
<span id="cb6-8">) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb6-9">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_point</span>()</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-curves_datetime" class="quarto-float quarto-figure quarto-figure-center anchored" width="672">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-curves_datetime-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="index_files/figure-html/fig-curves_datetime-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure&nbsp;1: "><img src="https://www.lukehearon.com/blog/2026/buzzdetect-walkthrough/index_files/figure-html/fig-curves_datetime-1.png" id="fig-curves_datetime" class="img-fluid figure-img" width="672"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-curves_datetime-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1
</figcaption>
</figure>
</div>
</div>
</div>
<p>Aha! Here’s a common issue: all of these recordings were taken at different times of the season. How are we supposed to plot them on a common X axis? We could coerce the hour and minute to a numeric value so that 0.50 is noon, but then our X axis would be difficult to interpret. buzzr has a convenience function, <code>buzzr::commontime</code> that forces the year, month, and day of any input time value to 2000-01-01, allowing for pretty plotting. Let’s try it.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb7-1">results_plot <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> results <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span> </span>
<span id="cb7-2">  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># we'll smooth out the curves for nicer plotting</span></span>
<span id="cb7-3">  buzzr<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">bin</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">calculate_rate =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span> </span>
<span id="cb7-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">commontime =</span> buzzr<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">commontime</span>(bin_datetime))</span>
<span id="cb7-5"></span>
<span id="cb7-6"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(</span>
<span id="cb7-7">  results_plot,</span>
<span id="cb7-8">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(</span>
<span id="cb7-9">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> commontime,</span>
<span id="cb7-10">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> detectionrate_ins_buzz,</span>
<span id="cb7-11">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> flower</span>
<span id="cb7-12">  )</span>
<span id="cb7-13">) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb7-14">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_point</span>()</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-curves_commontime" class="quarto-float quarto-figure quarto-figure-center anchored" width="672">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-curves_commontime-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="index_files/figure-html/fig-curves_commontime-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Figure&nbsp;2: "><img src="https://www.lukehearon.com/blog/2026/buzzdetect-walkthrough/index_files/figure-html/fig-curves_commontime-1.png" id="fig-curves_commontime" class="img-fluid figure-img" width="672"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-curves_commontime-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2
</figcaption>
</figure>
</div>
</div>
</div>
<p>Much better. Now, for full beautification.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb8-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(</span>
<span id="cb8-2">  results_plot,</span>
<span id="cb8-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(</span>
<span id="cb8-4">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> commontime,</span>
<span id="cb8-5">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> detectionrate_ins_buzz,</span>
<span id="cb8-6">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> flower,</span>
<span id="cb8-7">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">group =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">interaction</span>(flower, recorder)</span>
<span id="cb8-8">  )</span>
<span id="cb8-9">) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb8-10">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_path</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb8-11">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">facet_grid</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">rows=</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vars</span>(flower), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">scales=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'free_y'</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb8-12">  buzzr<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme_buzzr</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb8-13">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_x_datetime</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">labels=</span>buzzr<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">label_hour</span>(), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">expand=</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">expansion</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb8-14">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_y_continuous</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">expand=</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">expansion</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">mult=</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>,<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>))) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb8-15">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">xlab</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'time of day'</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb8-16">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ylab</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'detection rate'</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb8-17">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">legend.position=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'none'</span>)</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-curves" class="quarto-float quarto-figure quarto-figure-center anchored" width="672">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-curves-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="index_files/figure-html/fig-curves-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="Figure&nbsp;3: "><img src="https://www.lukehearon.com/blog/2026/buzzdetect-walkthrough/index_files/figure-html/fig-curves-1.png" id="fig-curves" class="img-fluid figure-img" width="672"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-curves-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3
</figcaption>
</figure>
</div>
</div>
</div>
<p>Lovely!</p>
</section>
<section id="point-estimates" class="level3">
<h3 class="anchored" data-anchor-id="point-estimates">Point estimates</h3>
<p>Sometimes we don’t care about time, we just want to compare total activity rates.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb9-1">results_summary <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> results <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span> </span>
<span id="cb9-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">group_by</span>(flower, recorder) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span> </span>
<span id="cb9-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">summarize</span>(</span>
<span id="cb9-4">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">frames =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sum</span>(frames),</span>
<span id="cb9-5">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">detections_ins_buzz =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sum</span>(detections_ins_buzz)</span>
<span id="cb9-6">  )</span></code></pre></div></div>
</div>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb10-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(</span>
<span id="cb10-2">  results_summary,</span>
<span id="cb10-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(</span>
<span id="cb10-4">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> flower,</span>
<span id="cb10-5">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> detections_ins_buzz,</span>
<span id="cb10-6">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> flower,</span>
<span id="cb10-7">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> flower</span>
<span id="cb10-8">  )</span>
<span id="cb10-9">) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb10-10">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_boxplot</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">linewidth=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">alpha=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb10-11">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_y_continuous</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">labels =</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(l){<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">format</span>(l, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">big.mark=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">','</span>)}) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb10-12">  buzzr<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme_buzzr</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb10-13">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">legend.position =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'none'</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb10-14">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ylab</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'total</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">detections'</span>)</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-boxplot" class="quarto-float quarto-figure quarto-figure-center anchored" width="576">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-boxplot-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="index_files/figure-html/fig-boxplot-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="Figure&nbsp;4: "><img src="https://www.lukehearon.com/blog/2026/buzzdetect-walkthrough/index_files/figure-html/fig-boxplot-1.png" id="fig-boxplot" class="img-fluid figure-img" width="576"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-boxplot-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4
</figcaption>
</figure>
</div>
</div>
</div>
<p>Not bad, but what’s with the outliers? Chicory, pumpkin, and watermelon all have dots above the whisker, which are far from the center of the data.</p>
<p>The issue is, these data aren’t remotely normal. In fact, they’re bounded on both ends: you can’t have fewer than 0 detections, but you also can’t have more detections than you have frames. The true nature of these data is a rate, the number of positive frames divided by the total number of frames. We call this the detection rate. A sampling of detection rates near 0.50 is roughly normal, b</p>
<p>As a rate, the data are (0,1) bounded and so they necessarily press up against 0.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb11-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ggplot</span>(</span>
<span id="cb11-2">  results_summary,</span>
<span id="cb11-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aes</span>(</span>
<span id="cb11-4">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">x =</span> flower,</span>
<span id="cb11-5">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">y =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">log10</span>(detections_ins_buzz),</span>
<span id="cb11-6">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fill =</span> flower,</span>
<span id="cb11-7">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">color =</span> flower</span>
<span id="cb11-8">  )</span>
<span id="cb11-9">) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb11-10">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geom_boxplot</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">linewidth=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">alpha=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.2</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb11-11">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">scale_y_continuous</span>(</span>
<span id="cb11-12">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">labels =</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(l){<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">format</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">^</span>l, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">big.mark=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">','</span>)},</span>
<span id="cb11-13">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">breaks =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">log10</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">500</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1000</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2000</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5000</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10000</span>))</span>
<span id="cb11-14">  ) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb11-15">  buzzr<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme_buzzr</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">14</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb11-16">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">theme</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">legend.position =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'none'</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span></span>
<span id="cb11-17">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ylab</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'total</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">detections</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">(log scale)'</span>)</span></code></pre></div></div>
<div class="cell-output-display">
<div id="fig-boxplot_log" class="quarto-float quarto-figure quarto-figure-center anchored" width="576">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-boxplot_log-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<a href="index_files/figure-html/fig-boxplot_log-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-6" title="Figure&nbsp;5: "><img src="https://www.lukehearon.com/blog/2026/buzzdetect-walkthrough/index_files/figure-html/fig-boxplot_log-1.png" id="fig-boxplot_log" class="img-fluid figure-img" width="576"></a>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig quarto-uncaptioned" id="fig-boxplot_log-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;5
</figcaption>
</figure>
</div>
</div>
</div>
<p>I haven’t found a great way to plot distributions of detection rates. Linear axes squish the data. Log axes are annoying to interpret. Boxplots imply “outliers” that are completely expected. Violin plots promise more than they deliver. The reality is that these data are beta distributed and we might be best off plotting the confidence intervals of the precision and mean parameters, but that makes a very inaccessible figure. My best advice for now: just do a boxplot on the linear scale make sure to note that the outlier dots are completely expected.</p>
</section>
</section>
<section id="step-four-model" class="level2">
<h2 class="anchored" data-anchor-id="step-four-model">Step Four: Model</h2>
<section id="detection-rates" class="level3">
<h3 class="anchored" data-anchor-id="detection-rates">Detection rates</h3>
<div class="callout callout-style-default callout-warning callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Warning
</div>
</div>
<div class="callout-body-container callout-body">
<p>Be careful when interpreting the results from detection rates. Bioacoustics demans a number of unique considerations. Detections are downstream of pollinator loudness, background noise, the amount of buzzing produced by an individual visit (which is downstream of floral morphology), and the quality of buzzing (i.e., different pollinators will produce different sounds). Any of these can become confounds if the experimental design does not account for it. For example, the same number of bees is certain to produce more buzzes in mustard (with numerous, small flowers that they fly between) than it will in watermelon (with fewer, larger flowers that they can land on and walk into). The stats in this section are just demonstrative.</p>
</div>
</div>
<p>Here, we’ll model samples of detection rates. By this I mean not the activity rate over time, but “given recorders in this group and recorders in that group, what was the difference in detections? Remember: summed detection rates look like counts, but they’re actually rates. You can’t have fewer than 0, you can’t have more than the number of your frames.<span class="tippy html-widget html-fill-item" height="338px" id="htmlwidget-51c8bd18c72c9d76204b" width="100%"></span> <script type="application/json" data-for="htmlwidget-51c8bd18c72c9d76204b">{"x":{"opts":{"animation":"scale","duration":70,"allowHTML":true,"interactive":true,"content":"In fact, most rate models can't include 0 or 1. If you're recording for more than a few hours, its vanishingly unlikely you'll see exactly 0 detections and even a mic in front of an active beehive doesn't produce a detection rate of 1.0"},"text":"<span style='color: var(--clr-tooltip);'><sup>※<\/sup><\/span>"},"evals":[],"jsHooks":[]}</script> As such, we should generally use beta regression, although sometimes linear regression may work. If beta regressions are new to you, Andrew Heiss has <a href="https://www.andrewheiss.com/blog/2021/11/08/beta-regression-guide/">a wonderful guide</a> to get you started.</p>
<p>Let’s modify the results_summary we made in the plotting section to calculate our detection rate.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb12-1">results_summary<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>detectionrate_ins_buzz <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> results_summary<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>detections_ins_buzz<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span>results_summary<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>frames</span></code></pre></div></div>
</div>
<section id="fixed-beta-model" class="level4">
<h4 class="anchored" data-anchor-id="fixed-beta-model">Fixed beta model</h4>
<p>Let’s start with a simple model using the package <a href="https://cran.r-project.org/web/packages/betareg/vignettes/betareg.html">betareg</a>.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb13-1">model_fixed <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> betareg<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">betareg</span>(</span>
<span id="cb13-2">  detectionrate_ins_buzz <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span> flower,</span>
<span id="cb13-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> results_summary</span>
<span id="cb13-4">)</span></code></pre></div></div>
</div>
<p>We’ll use <a href="https://cran.r-project.org/web/packages/emmeans/index.html">emmeans</a> to rigorously test this model. Learn more about statistical testing with emmeans <a href="https://cran.r-project.org/web/packages/emmeans/vignettes/comparisons.html">here</a>.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb14-1">contrast_fixed <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> emmeans<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">emmeans</span>(model_fixed, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">specs =</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>flower) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb14-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">pairs</span>()</span>
<span id="cb14-3"></span>
<span id="cb14-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(contrast_fixed)</span></code></pre></div></div>
<div class="cell-output-display">
<div>
<figure class="figure">
<p><a href="index_files/figure-html/unnamed-chunk-4-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-7"><img src="https://www.lukehearon.com/blog/2026/buzzdetect-walkthrough/index_files/figure-html/unnamed-chunk-4-1.png" class="img-fluid figure-img" width="672"></a></p>
</figure>
</div>
</div>
</div>
<p>The blue lines are our 95% confidence intervals for the pairwise contrast. Where the blue lines don’t cross 0, we can consider the two detection rates to be significantly different at the α = 0.05 level. As a filtered table:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb15-1">contrast_fixed <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb15-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.data.frame</span>() <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb15-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filter</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">`</span><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">p.value</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">`</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.05</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code> contrast                estimate          SE  df z.ratio p.value
 chicory - mustard    -0.07853511 0.010148965 Inf  -7.738  &lt;.0001
 chicory - soybean    -0.03613594 0.007717661 Inf  -4.682  &lt;.0001
 mustard - pumpkin     0.08165092 0.009984653 Inf   8.178  &lt;.0001
 mustard - soybean     0.04239918 0.011949612 Inf   3.548  0.0036
 mustard - watermelon  0.07700177 0.010231059 Inf   7.526  &lt;.0001
 pumpkin - soybean    -0.03925174 0.007500868 Inf  -5.233  &lt;.0001
 soybean - watermelon  0.03460259 0.007825124 Inf   4.422  0.0001

P value adjustment: tukey method for comparing a family of 5 estimates </code></pre>
</div>
</div>
<p>The estimate here is the effect on the mean parameter of the beta regression. This is not the same thing as the effect on the detection rate. Read Heiss’s <a href="https://www.andrewheiss.com/blog/2021/11/08/beta-regression-guide/">guide</a> for more info.</p>
</section>
<section id="mixed-beta-model" class="level4">
<h4 class="anchored" data-anchor-id="mixed-beta-model">Mixed beta model</h4>
<p>Your experimental design may be hierarchical or contain repeated measures. betareg does not allow for random effects. To account for random effects in a beta regression, you’ll need to use <a href="https://cran.r-project.org/web/packages/brms/index.html">glmmTMB</a>. We <em>don’t</em> have random effects in this dataset, but if for example we collected data from multiple sites, it would look like:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb17-1">model_mixed <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> glmmTMB<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">glmmTMB</span>(</span>
<span id="cb17-2">  detectionrate_ins_buzz <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span> flower <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> site),</span>
<span id="cb17-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> results_summary,</span>
<span id="cb17-4">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">family =</span> glmmTMB<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">beta_family</span>()</span>
<span id="cb17-5">)</span></code></pre></div></div>
<p>And we could test with emmeans using a similar approach.</p>
</section>
<section id="linear-model" class="level4">
<h4 class="anchored" data-anchor-id="linear-model">Linear model</h4>
<p>Rates aren’t normally distributed, but they might not be far off. I always encourage using the most appropriate statistical model, but linear regressions are robust to violations of the assumption of normality, so we can linearly model rates as a quick-and-dirty test.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb18-1">model_linear <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">lm</span>(</span>
<span id="cb18-2">  detectionrate_ins_buzz <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span> flower,</span>
<span id="cb18-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> results_summary</span>
<span id="cb18-4">)</span></code></pre></div></div>
</div>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb19-1">car<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">qqPlot</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">resid</span>(model_linear))</span></code></pre></div></div>
<div class="cell-output-display">
<div>
<figure class="figure">
<p><a href="index_files/figure-html/unnamed-chunk-6-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-8"><img src="https://www.lukehearon.com/blog/2026/buzzdetect-walkthrough/index_files/figure-html/unnamed-chunk-6-1.png" class="img-fluid figure-img" width="672"></a></p>
</figure>
</div>
</div>
</div>
<p>Good enough for most reviewers! If your rates are closer to 0, your residuals will be less normal. Even in this case, taking the log is usually close enough to pass a Q-Q plot. Again, I still encourage modeling your data appropriately, especially because the parameters of a beta regression are much more informative than those of a pseudo-linear model.</p>
</section>
</section>
<section id="time-of-day" class="level3">
<h3 class="anchored" data-anchor-id="time-of-day">Time of day</h3>
<p>One of the most compelling applications of bioacoustics is for fine-resolution phenology. It sure looks like the flowers differ in timing. Is it statistically significant?</p>
<p>Let’s try out a model. I find that the peak times of detections per recorder are reasonably normally distributed, so we can use a simple linear model. Unfortunately, the low detection rate in pumpkin leads to noise that hides the underlying trend, so we’ll drop it from this analysis.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb20-1">peaktimes <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> results <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span> </span>
<span id="cb20-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">group_by</span>(flower, recorder) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span></span>
<span id="cb20-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">slice_max</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">order_by=</span>detections_ins_buzz, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">with_ties =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span> </span>
<span id="cb20-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">time_peak =</span> buzzr<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">time_of_day</span>(bin_datetime, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">time_format=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'hour'</span>))</span></code></pre></div></div>
</div>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb21-1">model_peaks <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">lm</span>(</span>
<span id="cb21-2">  time_peak <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span> flower,</span>
<span id="cb21-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">data =</span> peaktimes <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%&gt;%</span> </span>
<span id="cb21-4">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filter</span>(flower<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'pumpkin'</span>)</span>
<span id="cb21-5">)</span></code></pre></div></div>
</div>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb22" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb22-1">car<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">qqPlot</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">resid</span>(model_peaks))</span></code></pre></div></div>
<div class="cell-output-display">
<div>
<figure class="figure">
<p><a href="index_files/figure-html/unnamed-chunk-8-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-9"><img src="https://www.lukehearon.com/blog/2026/buzzdetect-walkthrough/index_files/figure-html/unnamed-chunk-8-1.png" class="img-fluid figure-img" width="672"></a></p>
</figure>
</div>
</div>
</div>


</section>
</section>

 ]]></description>
  <category>walkthroughs</category>
  <guid>https://www.lukehearon.com/blog/2026/buzzdetect-walkthrough/</guid>
  <pubDate>Mon, 30 Mar 2026 04:00:00 GMT</pubDate>
  <media:content url="https://www.lukehearon.com/blog/2026/buzzdetect-walkthrough/buzzdetect.png" medium="image" type="image/png" height="62" width="144"/>
</item>
<item>
  <title>A walkthrough for conducting research with buzzdetect</title>
  <link>https://www.lukehearon.com/blog/2026/buzzr/</link>
  <description><![CDATA[ 




<p>Having now analyzed a lot of buzzdetect results, I noticed that the first few R scripts for any bioacoustics project are almost identical. For my own convenience, I started building <a href="https://github.com/OSU-Bee-Lab/buzzr">an R package</a> to handle these common tasks. As the package developed, I figured it would be equally handy for other buzzdetect users,<span class="tippy html-widget html-fill-item" height="338px" id="htmlwidget-9e244e6b9463ab8883f1" width="100%"></span> <script type="application/json" data-for="htmlwidget-9e244e6b9463ab8883f1">{"x":{"opts":{"animation":"scale","duration":70,"allowHTML":true,"interactive":true,"content":"You know, once they exist."},"text":"<span style='color: var(--clr-tooltip);'><sup>※<\/sup><\/span>"},"evals":[],"jsHooks":[]}</script> so I built out the documentation and reworked the functions so that they make sense outside of the OSU Bee Lab.</p>
<p>After a lot of time renaming variables and writing docs<span class="tippy html-widget html-fill-item" height="338px" id="htmlwidget-a5b08600e23038799c2f" width="100%"></span> <script type="application/json" data-for="htmlwidget-a5b08600e23038799c2f">{"x":{"opts":{"animation":"scale","duration":70,"allowHTML":true,"interactive":true,"content":"How is it possible that documentation takes twice as long as development?"},"text":"<span style='color: var(--clr-tooltip);'><sup>※<\/sup><\/span>"},"evals":[],"jsHooks":[]}</script> , I’m happy to say that buzzr is ready to see the light of day! Check out the detailed vignette on <a href="https://osu-bee-lab.github.io/buzzr/articles/buzzr.html">the official documentation site here</a>.</p>
<p>Cheers!</p>



 ]]></description>
  <category>machine learning</category>
  <category>bioacoustics</category>
  <guid>https://www.lukehearon.com/blog/2026/buzzr/</guid>
  <pubDate>Fri, 27 Feb 2026 05:00:00 GMT</pubDate>
</item>
<item>
  <title>SeeNote: an audio-video event annotation tool</title>
  <link>https://www.lukehearon.com/blog/2026/seenote/</link>
  <description><![CDATA[ 




<p><a href="spec_labeled.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://www.lukehearon.com/blog/2026/seenote/spec_labeled.png" class="img-fluid"></a></p>
<p>Well, I didn’t expect my inaugural blog post would be about a vibe coded tool, but all the thoughtful labor I’m putting into my other posts is delaying them. <strong>TL;DR:</strong> “I” “made” a tool called <a href="../../../SeeNote/">SeeNote</a> for labeling audio events in video files. It’s free. Try it out!</p>
<section id="beyond-buzzes" class="level3">
<h3 class="anchored" data-anchor-id="beyond-buzzes">Beyond buzzes</h3>
<p>I train models that detect acoustic events (namely, pollinator flight buzzes). I’ve labeled countless<span class="tippy html-widget html-fill-item" height="338px" id="htmlwidget-f1536258dd11486a599c" width="100%"></span> <script type="application/json" data-for="htmlwidget-f1536258dd11486a599c">{"x":{"opts":{"animation":"scale","duration":70,"allowHTML":true,"interactive":true,"content":"Actually, 2,764 buzzes by my count.<br>Less than I thought..."},"text":"<span style='color: var(--clr-tooltip);'><sup>※<\/sup><\/span>"},"evals":[],"jsHooks":[]}</script> buzzes from field recordings, but it’s impossible to know for certain the identity of the insect making the buzz. Is it a honey bee? An andrenid bee? A small bumble bee? If we want our models to go beyond detection and towards classification, we need more information. Audio alone is not sufficient to train an acoustic model.</p>
<p>To identify the pollinator producing the buzz, we need video. But video comes with a whole host of headaches, one of which is difficulty of annotation. For annotating audio, we use the wonderful and free <a href="https://www.audacityteam.org/">Audacity</a>, using label tracks to tag events. For video, there’s not a satisfying equivalent. <a href="https://audavid.com/index.htm">AudaVid</a> looked promising but wasn’t a smooth experience when we tried it a couple years ago. <a href="https://archive.mpi.nl/tla/elan">ELAN</a> is extremely powerful, but dauntingly complex and the spectrogram panel seems to be broken. I’m sure the various paid enterprise solutions are great, but they’re also way more powerful than we need and unless any of these organizations wants to cut me a grant,<span class="tippy html-widget html-fill-item" height="338px" id="htmlwidget-e26493dc31c674037c54" width="100%"></span> <script type="application/json" data-for="htmlwidget-e26493dc31c674037c54">{"x":{"opts":{"animation":"scale","duration":70,"allowHTML":true,"interactive":true,"content":"<a href=\"mailto:hearon.9@buckeyemail.osu.edu?subject=Offering a bottomless grant&body=[put bank details or crypto wallet here].\">📧<\/a>"},"text":"<span style='color: var(--clr-tooltip);'><sup>※<\/sup><\/span>"},"evals":[],"jsHooks":[]}</script> they’re out of budget.</p>
<p>And as much as I love coding, I can only pull the wool so far over my advisor’s eyes. Building video annotation software as an entomology student is…a hard sell.</p>
</section>
<section id="sighlets-ask-the-robots" class="level3">
<h3 class="anchored" data-anchor-id="sighlets-ask-the-robots">Sigh…let’s ask the robots</h3>
<p><img src="https://www.lukehearon.com/blog/2026/seenote/SeeNote.png" class="img-fluid"> Holy smokes we’ve come a long way. I tried vibe coding this tool using <a href="https://www.jetbrains.com/junie/">JetBrains’ Junie</a> maybe a year ago<span class="tippy html-widget html-fill-item" height="338px" id="htmlwidget-c2de3b7d403267033e2e" width="100%"></span> <script type="application/json" data-for="htmlwidget-c2de3b7d403267033e2e">{"x":{"opts":{"animation":"scale","duration":70,"allowHTML":true,"interactive":true,"content":"Which, to be fair, is generations in AI-years. I don't think it was a Junie issue."},"text":"<span style='color: var(--clr-tooltip);'><sup>※<\/sup><\/span>"},"evals":[],"jsHooks":[]}</script> and it was hopeless. I used <a href="https://aistudio.google.com/apps">Google AI Studio</a> with Gemini 3 Pro Preview to create this tool. It perfectly one-shot my initial prompt, although now I’m iterating and refining.</p>
</section>
<section id="introducing-seenote" class="level3">
<h3 class="anchored" data-anchor-id="introducing-seenote">Introducing: <a href="../../../SeeNote/">SeeNote</a></h3>
<p>SeeNote is an audio-visual event annotation tool. The core philosophy is (i) synced audio+video+spectrogram playback, with (ii) event labeling, while being (iii) as simple and friendly as possible.</p>
<p>Disclaimer: this product is almost entirely vibe coded. I know nothing about react or webapps in general. All processing happens locally on your browser, but check the <a href="https://github.com/LukeHearon/SeeNote">source code</a> for yourself. I know vibe coding invites a lot of scorn. I’m entirely against it when it comes to data analysis<span class="tippy html-widget html-fill-item" height="338px" id="htmlwidget-7fdd829e2437350e6286" width="100%"></span> <script type="application/json" data-for="htmlwidget-7fdd829e2437350e6286">{"x":{"opts":{"animation":"scale","duration":70,"allowHTML":true,"interactive":true,"content":"Just as the process of writing is the process of thinking, so too is the process of coding. You can't interpret statistics without understanding how they were generated, from raw data to p-value."},"text":"<span style='color: var(--clr-tooltip);'><sup>※<\/sup><\/span>"},"evals":[],"jsHooks":[]}</script> and though I use LLMs extensively for troubleshooting and problem solving when coding, I haven’t pushed any vibe code to buzzdetect. All this said, if this tool outputs the correct labels, I don’t care how it happens under the hood. I’m guessing you don’t either, as long as it isn’t hiding any crypto miners or security vulnerabilities. Proof: I never inspected Audacity’s source code.</p>
<p>I didn’t make SeeNote, I just specified it for the machine to make. I waive all intellectual property rights I may have in this code and provide it to the public domain. You are free to use, copy, modify, and profit from it.</p>
<section id="features" class="level4">
<h4 class="anchored" data-anchor-id="features">Features<span class="tippy html-widget html-fill-item" height="338px" id="htmlwidget-fd8f434f05fa00a737a2" width="100%"></span> <script type="application/json" data-for="htmlwidget-fd8f434f05fa00a737a2">{"x":{"opts":{"animation":"scale","duration":70,"allowHTML":true,"interactive":true,"content":"SeeNote is under active vibe development. Features are subject to change or to inexplicably break as I <a href=https://www.youtube.com/watch?v=64TNGvCoegE>beg the robot to be a senior expert.<\/a>"},"text":"<span style='color: var(--clr-tooltip);'><sup>※<\/sup><\/span>"},"evals":[],"jsHooks":[]}</script></h4>
<section id="audiovideo-together-forever" class="level5">
<h5 class="anchored" data-anchor-id="audiovideo-together-forever">Audio+Video, together forever</h5>
<ul>
<li>Supports common video and audio filetypes</li>
<li>Synced video, audio, and spectrogram playback</li>
<li>Adjust frequency scale (linear, log, mel) and spectrogram visualization (frequency range, brightness, contrast)</li>
<li>Client-side processing - everything happens in your browser</li>
</ul>
</section>
<section id="easy-labels" class="level5">
<h5 class="anchored" data-anchor-id="easy-labels">Easy labels</h5>
<ul>
<li>Click-and-drag annotation directly on the spectrogram timeline</li>
<li>Define custom labels on hotkeys to quickly switch between common labels</li>
<li>Rename custom labels to</li>
<li>Intelligent auto-stacking prevents overlapping labels from obscuring each other</li>
</ul>
</section>
<section id="save-your-hard-work" class="level5">
<h5 class="anchored" data-anchor-id="save-your-hard-work">Save your hard work</h5>
<ul>
<li>Export results as…
<ul>
<li>Audacity Labels (.txt, tab separated and without column names)</li>
<li>CSV</li>
<li>JSON</li>
</ul></li>
</ul>
<p>Enjoy, and let me know if you’d like to see any new features!</p>


</section>
</section>
</section>

 ]]></description>
  <category>machine learning</category>
  <category>bioacoustics</category>
  <category>cool things</category>
  <guid>https://www.lukehearon.com/blog/2026/seenote/</guid>
  <pubDate>Wed, 28 Jan 2026 05:00:00 GMT</pubDate>
  <media:content url="https://www.lukehearon.com/blog/2026/seenote/SeeNote.png" medium="image" type="image/png" height="78" width="144"/>
</item>
</channel>
</rss>
