<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://md.ekstrandom.net/blog/</id>
    <title>I Fight for the Users</title>
    <updated>2026-06-15T00:28:18.226Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <author>
        <name>Michael Ekstrand</name>
        <email>md@ekstrandom.net</email>
        <uri>https://md.ekstrandom.net/</uri>
    </author>
    <link rel="alternate" href="https://md.ekstrandom.net/blog/"/>
    <subtitle>Michael Ekstrand's blog.</subtitle>
    <icon>https://md.ekstrandom.net/favicon.png</icon>
    <rights>Copyright Michael Ekstrand. CC-BY-NC-ND.</rights>
    <entry>
        <title type="html"><![CDATA[Academic Logbooks]]></title>
        <id>https://md.ekstrandom.net/blog/2026/05/logbooks</id>
        <link href="https://md.ekstrandom.net/blog/2026/05/logbooks"/>
        <updated>2026-05-15T04:00:00.000Z</updated>
        <content type="html"><![CDATA[<aside class="callout tldr">
<p>Keeping records of academic activity and effort, as you go, makes it
easier to prepare review materials and to understand your activity and
performance.</p>
</aside>
<figure class="right compact autosize">
<a href="/images/hms-dolphin-logbook.webp"><img srcset="/images/hms-dolphin-logbook-320.webp 1x, /images/hms-dolphin-logbook-480.webp 1.5x, /images/hms-dolphin-logbook-640.webp 2x" src="/images/hms-dolphin-logbook-640.webp" alt="Log book from the HMS Dolphin, Jan. 1765."></a>
<figcaption class="credit">
Public domain, from <a
href="https://commons.wikimedia.org/wiki/File:HMS_Dolphin_-_Log_Book_1.jpg">Wikimedia
Commons</a>.
</figcaption>
</figure>
<p>In a previous post, I wrote about <a
href="/blog/2022/02/portlolio">keeping an academic portfolio</a> to
maintain current records <em>and evidence files</em> of academic work
and output.</p>
<p>In this post, I want to describe another element I have added to
these portfolios: <span class="jawn-title">logbook spreadsheets</span>
that record not just confirmed outputs, but academic effort. These
spreadsheets make it easier to fill out my annual review materials,
midterm / tenure / promotion dossiers, etc., and also provide
possibly-useless statistics like my personal acceptance rates.</p>
<section id="teaching-logbook" class="level2">
<h2>Teaching Logbook</h2>
<p>My teaching logbook consists primarily of a sheet recording all of
the classes I teach, including:</p>
<ul>
<li>Term</li>
<li>Course (number and title)</li>
<li>Section (number, grad/undergrad, modality)</li>
<li>Enrollment</li>
<li>Student eval summary (instructor rating course rating)</li>
</ul>
<p>This table can then almost be copy-pasted as-is into portfolio or
dossier evidence sheets, and makes it easy to generate statistics (e.g.,
my department wants to see the average instructor &amp; course ratings
broken down by modality).</p>
</section>
<section id="research-logbook" class="level2">
<h2>Research Logbook</h2>
<p>My research logbook has two different primary log tables in it: one
for submitted papers, and another for submitted proposals.</p>
<section id="paper-log" class="level3">
<h3>Paper Log</h3>
<p>The paper submission log includes:</p>
<ol type="1">
<li>Submission date</li>
<li>Submission venue</li>
<li>Expected publication year</li>
<li>Submission type (journal, full conference, short, workshop,
etc.)</li>
<li>Outcome (pending, reject, accept, etc.)</li>
<li>Publication date (for accepted work)</li>
<li>Lead (who led this submission?)</li>
<li>Title</li>
</ol>
<p>I then use pivot tables to summarize submission by type and outcome,
or any other statistics that seem interesting or that will support
review materials I am preparing.</p>
</section>
<section id="proposal-log" class="level3">
<h3>Proposal Log</h3>
<p>The proposal log is similar, logging all grant submissions:</p>
<ol type="1">
<li>Funder</li>
<li>Program</li>
<li>Funder Type (public / nonprofit / corporate)</li>
<li>Submission Date</li>
<li>Status</li>
<li>Host Institution (where I was when I wrote the proposal, since I’ve
moved institutions a few times)</li>
<li>Lead Institution</li>
<li>Grant start date</li>
<li>Grant end date</li>
<li>Grant / proposal amount</li>
<li>Supplement amount (e.g. REU supplements on a funded grant)</li>
<li>Total amount (grant + supplement)</li>
<li>Local Share (portion of grant at my institution)</li>
<li>My Share (portion of grant I get credit for)</li>
<li>Role (PI, co-PI, Sr. Personnel, etc.)</li>
<li>Title / Topic</li>
</ol>
<p>This then supports counting proposals or totalling proposal amounts
by type, outcome, host, etc.</p>
</section>
<section id="coi-log" class="level3">
<h3>COI Log</h3>
<p>One final logbook is my “COI log”: a list of all of my collaborators
with a quick project key and last-active date. This is primarily to
support preparing COI lists for things like NSF proposal
submissions.</p>
<p>It has the following columns:</p>
<ol type="1">
<li>“Code” — the code used in the NSF COA spreadsheet (usually C or
A)</li>
<li>Name</li>
<li>Affiliation</li>
<li>Email</li>
<li>Why? — reason for collab: paper, ongoing collab, preprint, workshop,
permanent COI, etc.</li>
<li>Project — short project keyword, to make it easy to filter for
collabs on a particular project</li>
<li>Last Active — the date our collaboration was last active, often
empty, but used to mark end or pause of collab</li>
<li>Last Submitted — the last date we submitted something together</li>
<li>Last Published — the last date we published something together</li>
<li>Timeout — compute field with the number of months since last
activity / submission / publication.</li>
<li>Active — whether the collaboration is currently active, based on
reason, and whether a “Last Active” date has been set.</li>
<li>ACM COI — whether we have a current COI by ACM’s rules, based on
Active and Timeout (24 months).</li>
<li>NSF COI — same, except with ACM’s timeout rules (48 months)</li>
</ol>
<p>The timeout field uses the following Excel formula:</p>
<pre><code>=LET(refdt, MAX([@[Last Active]], [@[Last Submitted]], [@[Last Published]]),
     IF(AND(refdt &lt; TODAY(), refdt &gt;= DATE(2000, 1, 1)),
        DATEDIF(refdt, TODAY(), &quot;m&quot;), 0))</code></pre>
</section>
</section>
<section id="logging-service" class="level2">
<h2>Logging Service</h2>
<p>These ideas can also be extended to logging service activity. I do
not currently use spreadsheets for service logging, but it’s a good
idea. I do log my reviewing activity in an Obsidian document, and
automatically extract the review section of my CV from that task
list.</p>
</section>
<section id="concluding-thoughts" class="level2">
<h2>Concluding Thoughts</h2>
<p>As with saving documentation and evidence, it’s easier to summarize
and report on your activity if you keep records of it as you go. A few
spreadsheets can make that work, and also support personal academic
analytics as well as make it easier to prepare things like NSF COA
lists.</p>
</section>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[2025 State of the Tools]]></title>
        <id>https://md.ekstrandom.net/blog/2025/12/tools</id>
        <link href="https://md.ekstrandom.net/blog/2025/12/tools"/>
        <updated>2025-12-28T05:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="right autosize compact">
<a href="/images/unsplash-bike-tools.webp"><img srcset="/images/unsplash-bike-tools-320.webp 1x, /images/unsplash-bike-tools-480.webp 1.5x, /images/unsplash-bike-tools-640.webp 2x" src="/images/unsplash-bike-tools-640.webp" alt="various bicycle tools hanging on a wall"></a>
<figcaption class="credit">
Photo by
<a href="https://unsplash.com/@tonchik?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Anton
Savinov</a> on
<a href="https://unsplash.com/photos/a-bunch-of-tools-hanging-up-on-a-wall-2Qlj2Gaft7w?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>
</figcaption>
</figure>
<p>Goodbye 2025! This has not been an easy year for fairly obvious
reasons, and I’m not feeling as reflective at the end of the year as I
sometimes am, but I’ll try to provide at least some of my usual year-end
reporting.</p>
<p>The core of my toolset is pretty stable from <a
href="/blog/2023/12/tools">2023</a> and <a
href="/blog/2024/12/tools">2024</a>: still in the Apple ecosystem for
endpoints, and a lot of the major infrastructural pieces and small
utilities are still in play. I’ll therefore focus this review on the
interesting changes.</p>
<section id="programming" class="level2">
<h2>Programming</h2>
<p>The only particularly interesting change in my programming toolset
this year has been leaning more on ZSH shell scripts for various
automation things and even nontrivial logic. I wrote <a
href="/code/dumptruck/"><code>dumptruck(8)</code></a>, an encrypted
backup management tool I use for backing up some of my servers, in Z
Shell. I also replaced several simpler TypeScript programs and with ZSH
scripts, and wrote myself a small <a
href="https://codeberg.org/mdekstrand/stdlib.zsh">standard library</a>
to share code across the various scripts and repositories I use.</p>
<p>Shell script is notoriously difficult to write correctly, but both
Bash and ZSH provide quite a few features to make it easier if you’re
willing to depend on them. I target Bash for most scripts in
repositories others need to work on but fully rely on ZSH for things
that are mostly me. It’s my standard login shell and I have it installed
on all my machines, so the dependency isn’t a problem.</p>
<p>I also moved most of my TypeScript/JavaScript code from Deno back to
Node. There are many things I like about Deno as a platform, including
the first-class TypeScript support, but deployment and building is a
pain (they really only support their own binaries, and keeping the Conda
packages working was rather a lot of work; last I touched them they had
some outstanding build failures that I couldn’t find workarounds for).
They’re a VC-funded product, too, which seems to bring various forms of
misalignment between community and funder needs. I was also extremely
annoyed by their deployment of an AI bot on their support forums to barf
incorrect information as a first reply to user questions. I’ve been
toying with the idea of using Haskell + Hakyll to build my website
again.</p>
</section>
<section id="git-hosting" class="level2">
<h2>Git Hosting</h2>
<p>Last year, I was using <a
href="https://github.com/charmbracelet/soft-serve"><code>soft-serve</code></a>
for hosting my private Git repositories (esp. with many LFS files), and
early this year I set up <a
href="https://github.com/ohwgiles/laminar">Laminar</a> to run CI jobs to
rebuild my website. They’re both nice pieces of software, and fine to
use if you don’t need anything more than command-line Git hosting and CI
jobs.</p>
<p>However, I started needing more than that. This month I migrated to a
self-hosted <a href="https://forgejo.org">Forgejo</a> instance for my
local Git hosting along with <a
href="https://woodpecker-ci.org">Woodpecker</a> for CI. Now that I have
the Woodpecker runners stable and the relevant permissions and caching
to reliably and efficiently rebuild my website, I’m pretty happy with
this setup. Forgejo also provides package repository support for
probably a dozen different package systems, including Debian
<code>apt</code>, RPM/DNF, Alpine Linux, and Docker, so it serves as a
private container image registry as well as Git and Git-LFS hosting.
This was my primary impetus for the switch, as I am relying on quite a
bit of containerization in my home network setup.</p>
<aside class="callout note">
Woodpecker CI also handles builds for my new project,
<a href="https://nods.ekstrandom.net">Notes on Data Science</a>.
</aside>
<p>I’ve also started moving some of my public projects from GitHub to <a
href="https://codeberg.org/mdekstrand/">Codeberg</a>. GitHub is still
super useful, and its performance and community reach are unrivaled, but
I’m concerned about the reorganization to put it under the Microsoft AI
division. I still keep things that I hope to build community around on
GitHub, particularly LensKit, but my little personal things are usually
going to Codeberg instead now.</p>
</section>
<section id="tasks-and-dependencies" class="level2">
<h2>Tasks and Dependencies</h2>
<p>Early this year, I discovered <a
href="https://mise.jdx.dev">mise-en-place</a>, a package that can
install software (from multiple different sources, including a registry
I hadn’t heard of before called <a
href="https://aquaproj.github.io">Aqua</a>), manage project
environments, and handle task running. I’ve adopted it for managing my
home directory software and across many of my projects. It has largely
replaced <code>direnv</code>, <code>just</code>, and several uses of
<code>pixi</code> or <code>conda</code> in my workflows. Simple task
workflows are easier to write in <code>just</code>, but as tasks get
more complex, <code>mise</code>’s support for writing tasks as
standalone shell scripts provides a better complexity scale-up story.
For several of my projects now, the interesting project-local
development and management scripts are ZSH scripts written to be run by
<code>mise</code>.</p>
<p>Mise provides help, dependencies, and command-line parsing (through
<a href="https://usage.jdx.dev"><code>usage</code></a>, which can also
be used on its own), so it’s pretty easy to write interesting and
reasonably well-documented automations with Mise.</p>
<p>Mise also introduced me to <a
href="https://getsops.io/docs/">sops</a>, which turns out to be a great
way to manage secrets in a project repository. I’ve now been using it,
both with Mise and on its own, to manage secrets for several projects
(including this website).</p>
<p>As both <a href="https://docs.astral.sh/uv/">uv</a> and published
Python packages have improved in quality, I’ve stopped using Conda or
Pixi for most projects. I’ll still use <a
href="https://pixi.sh/">Pixi</a> for projects with significant
non-Python dependencies I want version-locked, but installing scientific
Python packages from PyPI works a lot better now than it did a few years
ago, and I have slightly fewer difficulties with it than I do with
Pixi/Conda. Standard Python installations with uv’s dependency locking
work great for a lot of things. For solo projects, I then use Mise or
Aqua to pull in other dependencies if needed (or put them in a
container).</p>
</section>
<section id="home-computing-platform" class="level2">
<h2>Home Computing Platform</h2>
<p>Concurrently with moving to Forgejo, I also reworked our home
networking environment. I plan to write a separate post about this once
a few more pieces are finished, but I moved both our home and cloud
servers from Debian to <a href="https://almalinux.org">AlmaLinux</a> to
take advantage of <a href="https:/bootc-dev.github.io/bootc/">bootc</a>.
Some years ago I used NixOS, and wanted for some time a way to
declaratively manage my system configurations with a more normal Linux
and without the weirdness of the Nix expression language. I’ve
experimented with things like Ansible and PyInfra to automate
management, but true declarative management they are not.</p>
<p>Turns out <code>bootc</code> scratches that itch! It lets me manage
system state as <em>bootable containers</em>, so I write a
<code>Containerfile</code> (and associated scripts, configuration, etc.)
to assemble my system as an OCI (Docker) image, and <code>bootc</code>
installs and upgrades the system with that image. Mutable local state
lives in <code>/var</code> (and <code>/etc</code> by default, but I’m
working towards the recommended configuration of making
<code>/etc</code> fully immutable/ephemeral as well).</p>
<p>When this migration is finished, I think I’ll have achieved my home
admin goal: a git repository defining the software load and
configurations for the various servers on our network, making changes
through configuration edits + deploys. Forgejo’s container registry
support plays a vital role here, as the container-based systems install
and update from it.</p>
<p>I also replaced Tailscale with <a
href="https://nebula.defined.net">Nebula</a>. Tailscale is great, I
highly recommend it and have few complaints. I mainly wanted slightly
different firewall / access control capabilities than it provides; I
also have a small amount of nervousness about two
single-points-of-failure (Tailscale and GitHub authentication) for my
network security, so managing my overlay network in a self-hosted Git
repository of machine definitions and certificates is a nice benefit.
Nebula also provides front-line security for sensitive network services:
anything I don’t want chatted at by unsecured devices is only on the
Nebula network, even within our LAN, so it’s secured from the various
IoT devices and other things that might be on the network. I’ll be using
WireGuard to provide myself a roaming VPN for travel and off-site access
(accessing Nebula from outside our home network is no problem, but it
doesn’t support exit nodes like Tailscale, so a full exit to protect
from dodgy Wi-Fi needs another solution).</p>
<p>With Windows 10 end-of-life this year, I switched our desktop over to
Fedora. It runs (older) Steam games fine, and we don’t have a burning
need to upgrade the machine.</p>
</section>
<section id="document-preparation" class="level2">
<h2>Document Preparation</h2>
<p>This year I also started exploring <a
href="https://typst.app">Typst</a>, and love it. My use of LaTeX has
increasingly been begrudging over the last few years, and Typst is the
most promising entrant in a while to try to deliver strong typesetting
capabilities with a programming language that isn’t screamingly old in
its design principles. My <a href="/cv.pdf">CV</a> is now in Typst, and
I’m using it for other documents that I don’t need to collaborate on. I
plan to use it for a lot of my course materials next term (and possibly
also my lecture slides, we will see — I like PowerPoint, but
PowerPoint’s lack of style support makes it annoyingly difficult to
consistently typeset code snippets).</p>
<p>It isn’t ready to replace LaTeX for writing papers for ACM
conferences and journals, but I wouldn’t be surprised if it gets there
in the next five years (whether ACM’s backend will support it is another
question, whose positive resolution is less likely).</p>
<p>I have always found it far more painful than worthwhile to attempt to
customize LaTeX appearance beyond changing fonts and the page/layout
customization supported by the <a
href="https://ctan.org/pkg/memoir">Memoir class</a>. With Typst, making
a fully-custom layout and theme for my CV, and another one to implement
Drexel letterhead, was pleasant work.</p>
</section>
<section id="web-serving" class="level2">
<h2>Web Serving</h2>
<p>I was not happy with my web server situation this year:</p>
<ul>
<li>Apache is reliable for many things, and its interesting
<code>mod_speling</code> module allowed me to self-host my website while
maintaining URL compatibility with my past hosting on Netlify, which has
the interesting and questionable behavior of forcing all URLs to
lowercase. However, its “managed domain” support for automatic HTTPS is
glitchy and annoying, requiring multiple server reboots to bring a new
domain online.</li>
<li>Caddy has outstanding automatic HTTPS support, and supports 95% of
my web serving needs, but it doesn’t have an equivalent to
<code>mod_speling</code>, and I don’t know Go so writing a plugin to add
that support was going to require learning both a new programming
language and the Caddy API. And while I’m rarely one to shy away from a
new programming language, I don’t find Go to be a very interesting
language (although it’s used for a lot of great software!), so learning
it isn’t a priority for me.</li>
</ul>
<p>So I did the obvious thing, and wrote a (small) web server. The <a
href="https://codeberg.org/mdekstrand/opserve/">Opinionated Page
Server</a> (<code>opserve</code>) serves static files using a
pre-generated manifest of files (with their types and hashes), with
support for case-folding. It probably isn’t very useful to others, and
is currently undocumented, but it does what I need it to and happily
serves up my website from a container. It also gave me an excuse to play
with some more async Rust. The whole thing took maybe a couple of days
to write (using <a href="https://trillium.rs/">Trillium</a>). It only
supports plain HTTP, so I have Caddy in front of it as an
SSL-terminating reverse proxy (and Bunny CDN in front of that).</p>
</section>
<section id="music" class="level2">
<h2>Music</h2>
<p>I continue my vinyl music collection and listening hobby, and this
year upgraded my stereo receiver: replaced the Sony STR-DH180 with a
used Marantz NR1200. The Marantz has noticeably better sound quality,
especially for anything digital (even though the Sony supported hi-fi
audio over Bluetooth, its quality was pretty poor). I’m still looking to
upgrade my speakers, or at least the subwoofer, but I’m pretty happy
with most of the sound now.</p>
<p>I replaced the Shure open-back headphones I was using at home with
another pair of Sennheiser HD-600s (I already had a pair in my Drexel
office), for both weight and sound reasons.</p>
</section>
<section id="coming-attractions" class="level2">
<h2>Coming Attractions</h2>
<p>I’m always tweaking and improving (or breaking) things, as regular
readers may have noticed 🙃. In the next months, I hope to finish the
network upgrade (particularly adding the router to the
declaratively-managed set and implementing SSO, which is the last major
piece needed to switch to ephemeral <code>/etc</code> on managed
machines), and I’ll probably write some more random scripts and Rust
programs.</p>
</section>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Assigning Reviewers]]></title>
        <id>https://md.ekstrandom.net/blog/2025/05/assigning-reviewers</id>
        <link href="https://md.ekstrandom.net/blog/2025/05/assigning-reviewers"/>
        <updated>2025-05-27T04:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="autosize right compact">
<a href="/images/unsplash-spreadsheet.jpg"><img srcset="/images/unsplash-spreadsheet-320.jpg 1x, /images/unsplash-spreadsheet-480.jpg 1.5x, /images/unsplash-spreadsheet-640.jpg 2x" src="/images/unsplash-spreadsheet-640.jpg" alt="A spreadsheet full of numbers."></a>
<figcaption class="credit">
Photo by
<a href="https://unsplash.com/@kommumikation?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Mika
Baumeister</a> on
<a href="https://unsplash.com/photos/white-printing-paper-with-numbers-Wpnoqo2plFA?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a>
</figcaption>
</figure>
<p>Ph.D. admissions committees, faculty search committees, etc. need to
review dozens to hundreds of applications for a limited number of
positions, and need to do so in a way that gives each applicant a fair
review and keeps committee workloads manageable.</p>
<p>The way we’ve done this in a couple of committees I’ve been on is to
implement two-stage review: each application is first read by two
committee members, and if at least one of them thinks it merits further
consideration, it moves to the next stage (full committee review,
involving potential advisers, etc.). This requires us to assign those
initial reviewers, however.</p>
<hr class="fold">
<aside class="callout note">
Why 2? Two initial reviewers means that an applicant isn’t rejected just
because their application was seen by the one reviewer on the committee
who doesn’t resonate with it, while keeping the overall reviewing
workload managable.
</aside>
<p>A simple, procedurally-fair way to do this independent of the
application’s specific content is to randomly assign the initial
reviewers. I built an Excel spreadsheet to support this that we’ve used
for a couple of years for our Ph.D. admissions process. This post is to
document that spreadsheet and share it with whoever might find it
useful.</p>
<ul>
<li><a
href="https://view.officeapps.live.com/op/view.aspx?src=https%3A%2F%2Fmd.ekstrandom.net%2Ffiles%2Fresources%2FReviewerAssignmentDemo.xlsx">View
the workbook</a> (<a
href="/files/resources/ReviewerAssignmentDemo.xlsx">local
download</a>)</li>
</ul>
<section id="design-goals" class="level2">
<h2>Design Goals</h2>
<p>Given a spreadsheet containing application information, our goal is
to randomly assign 2 reviewers (out of
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>N</mi><annotation encoding="application/x-tex">N</annotation></semantics></math>)
to each distinct application. We also want to do this in a way that is
robust to reordering applications in the sheet, changing or replacing
cells, etc., so that the assignments remain stable.</p>
<p>The easy way to achieve this would be to use the application ID as
the seed to a random number generator, and then use that RNG to select
reviewers. Unfortunately, Excel’s <code>RAND</code> function does not
support explicit seeds, so we can’t do that.</p>
<aside class="callout note">
Excel’s nascent Python support might offer a very good solution for this
problem, but it isn’t currently widely-available, and is not yet enabled
in Drexel’s subscription.
</aside>
</section>
<section id="enter-the-lcg" class="level2">
<h2>Enter the LCG</h2>
<p>If Excel doesn’t give us a seedable RNG, can we make one? Yes!</p>
<p>A <a
href="https://en.wikipedia.org/wiki/Linear_congruential_generator">linear
congruential generator</a> is one of the simplest pseudorandom number
generator designs, and it’s simple enough to be implemented as Excel
formulas. The basic ideas is to start with a seed
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><msub><mi>X</mi><mn>0</mn></msub><annotation encoding="application/x-tex">X_0</annotation></semantics></math>,
and then compute subsequent numbers by
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>X</mi><mi>i</mi></msub><mo>=</mo><mo stretchy="false" form="prefix">(</mo><mi>a</mi><msub><mi>X</mi><mrow><mi>i</mi><mo>−</mo><mn>1</mn></mrow></msub><mo>+</mo><mi>c</mi><mo stretchy="false" form="postfix">)</mo><mrow><mspace width="0.222em"></mspace><mrow><mi mathvariant="normal">mod</mi><mo>&#8289;</mo></mrow><mspace width="0.222em"></mspace><mi>m</mi></mrow></mrow><annotation encoding="application/x-tex">X_i = (a X_{i-1} + c) \bmod m</annotation></semantics></math>.
It isn’t a <em>great</em> random number generator, but it’s good enough
for what we need.</p>
<p>Using the application number as the seed, instead of just generating
a number for each application, makes our assignments stable: given an
application number, we can directly compute its assignments.</p>
<p>Unfortunately, it isn’t quite that simple. An LCG on its own outputs
numbers in the range
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false" form="prefix">[</mo><mn>0</mn><mo>,</mo><mi>m</mi><mo stretchy="false" form="postfix">)</mo></mrow><annotation encoding="application/x-tex">[0,m)</annotation></semantics></math>,
but we only have
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>N</mi><annotation encoding="application/x-tex">N</annotation></semantics></math>
reviewers. To select two reviewers, we need both the two random numbers,
and we need to convert them into the range
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false" form="prefix">[</mo><mn>0</mn><mo>,</mo><mi>n</mi><mo stretchy="false" form="postfix">)</mo></mrow><annotation encoding="application/x-tex">[0,n)</annotation></semantics></math>
(and for the second, skip over the reviewer we picked in the first
step).</p>
<p>To implement this all of this, our <a
href="https://view.officeapps.live.com/op/view.aspx?src=https%3A%2F%2Fmd.ekstrandom.net%2Ffiles%2Fresources%2FReviewerAssignmentDemo.xlsx">workbook</a>
has two sheets: the applicant info sheet and the “AssignRNG” assignment
worksheet. “AssignRNG” implements the LCG, split out into different
columns of a table (<code>RVRNG</code>) that allow us to inspect the
LCG’s operation, and compute each of the reviewers.</p>
<p>On the right, you’ll find the parameters, with named cells holding
their values (i.e.,
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>m</mi><annotation encoding="application/x-tex">m</annotation></semantics></math>
is in the cell <code>P.m</code>). These parameters are:</p>
<ul>
<li><math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>m</mi><annotation encoding="application/x-tex">m</annotation></semantics></math>:
the LCG modulus</li>
<li><math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>a</mi><annotation encoding="application/x-tex">a</annotation></semantics></math>:
the LCG multiplier</li>
<li><math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>c</mi><annotation encoding="application/x-tex">c</annotation></semantics></math>:
the LCG increment / additive constant</li>
<li><em>off</em>
(<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>G</mi><annotation encoding="application/x-tex">G</annotation></semantics></math>):
an additive offset for converting entry numbers to seeds</li>
<li><em>mult</em>
(<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>H</mi><annotation encoding="application/x-tex">H</annotation></semantics></math>):
a multiplier for converting entry numbers to seeds</li>
</ul>
<p>The first three parameters are the core LCG parameters. Their values
can be a bit finicky; Excel has limited numeric range and precision, so
the values used by many implementations are too large for accurate
generation in Excel. The <a
href="https://en.wikipedia.org/wiki/Linear_congruential_generator">Wikipedia
article</a> has a table of values from different implementations. These
are from <code>random0</code>, an old implementation with a low period
(the generator will start repeating more quickly than you would want for
a lot of purposes), but they are in-range for Excel and good enough for
our purposes, especially since we are only generating two random numbers
from each seed.</p>
<p>The last two parameters are not LCG parameters, but control the seed
generation process. This control allows us to change the randomization
each year. The right portion of the sheet also contains the list of
reviewers, counts of the number of times each is assigned in the RNG
table, and counts of how often each pair of reviewers is assigned. These
counts allow lightweight randomization checks.</p>
<p>The RNG itself is a table (named <code>RVRNG</code>) with 7
columns:</p>
<ol type="1">
<li><p><code>Index</code> is the row number in the table. Each
application in the applications sheet is assigned a sequential
application number, starting with 1; we look up that number in this
column to determine that application’s assignment.</p></li>
<li><p><code>V1</code> and <code>V2</code> are the first and second
random numbers drawn from an LCG seeded with the <code>Index</code>.
They are computed as follows:</p>
<p><math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mtable><mtr><mtd columnalign="right" style="text-align: right; padding-right: 0"><mi>s</mi></mtd><mtd columnalign="left" style="text-align: left; padding-left: 0"><mo>=</mo><mi>G</mi><mo stretchy="false" form="prefix">(</mo><mi>i</mi><mo>+</mo><mi>H</mi><mo stretchy="false" form="postfix">)</mo></mtd></mtr><mtr><mtd columnalign="right" style="text-align: right; padding-right: 0"><msub><mi>V</mi><mn>1</mn></msub></mtd><mtd columnalign="left" style="text-align: left; padding-left: 0"><mo>=</mo><mo stretchy="false" form="prefix">(</mo><mi>a</mi><mi>s</mi><mo>+</mo><mi>c</mi><mo stretchy="false" form="postfix">)</mo><mrow><mspace width="0.222em"></mspace><mrow><mi mathvariant="normal">mod</mi><mo>&#8289;</mo></mrow><mspace width="0.222em"></mspace><mi>m</mi></mrow></mtd></mtr><mtr><mtd columnalign="right" style="text-align: right; padding-right: 0"><msub><mi>V</mi><mn>2</mn></msub></mtd><mtd columnalign="left" style="text-align: left; padding-left: 0"><mo>=</mo><mo stretchy="false" form="prefix">(</mo><mi>a</mi><msub><mi>V</mi><mn>1</mn></msub><mo>+</mo><mi>c</mi><mo stretchy="false" form="postfix">)</mo><mrow><mspace width="0.222em"></mspace><mrow><mi mathvariant="normal">mod</mi><mo>&#8289;</mo></mrow><mspace width="0.222em"></mspace><mi>m</mi></mrow></mtd></mtr><mtr><mtd columnalign="right" style="text-align: right; padding-right: 0"></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{align*}
s &amp; = G (i + H) \\
V_1 &amp; = (a s + c) \bmod m \\
V_2 &amp; = (a V_1 + c) \bmod m \\
\end{align*}</annotation></semantics></math></p></li>
<li><p><code>I1</code> and <code>I2</code> are the first and second
reviewer indices. They are computed by
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>I</mi><mn>1</mn></msub><mo>=</mo><msub><mi>V</mi><mn>1</mn></msub><mrow><mspace width="0.222em"></mspace><mrow><mi mathvariant="normal">mod</mi><mo>&#8289;</mo></mrow><mspace width="0.222em"></mspace><mi>N</mi></mrow></mrow><annotation encoding="application/x-tex">I_1 = V_1 \bmod N</annotation></semantics></math>
and
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>I</mi><mn>2</mn></msub><mo>=</mo><msub><mi>V</mi><mn>2</mn></msub><mrow><mspace width="0.222em"></mspace><mrow><mi mathvariant="normal">mod</mi><mo>&#8289;</mo></mrow><mspace width="0.222em"></mspace><mo stretchy="false" form="prefix">(</mo></mrow><mi>N</mi><mo>−</mo><mn>1</mn><mo stretchy="false" form="postfix">)</mo></mrow><annotation encoding="application/x-tex">I_2 = V_2 \bmod (N-1)</annotation></semantics></math>.
We use
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mo>−</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">N-1</annotation></semantics></math>
as the second modulus because we have already used one reviewer in the
first selection, so we need to select from the remaining
reviewers.</p></li>
<li><p><code>R1</code> selects the first reviewer, by using
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>I</mi><mn>1</mn></msub><mo>+</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">I_1 + 1</annotation></semantics></math>
to pick a reviewer from the list of reviewers.</p></li>
<li><p><code>R2</code> selects the second reviewer, by using
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>I</mi><mn>2</mn></msub><mo>+</mo><mi>k</mi></mrow><annotation encoding="application/x-tex">I_2 + k</annotation></semantics></math>
to pick a reviewer from the list of reviewers, where
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi><mo>=</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">k=1</annotation></semantics></math>
if
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>I</mi><mn>2</mn></msub><mo>&lt;</mo><msub><mi>I</mi><mn>1</mn></msub></mrow><annotation encoding="application/x-tex">I_2 &lt; I_1</annotation></semantics></math>
and
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi><mo>=</mo><mn>2</mn></mrow><annotation encoding="application/x-tex">k=2</annotation></semantics></math>
otherwise, to skip
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><msub><mi>I</mi><mn>1</mn></msub><annotation encoding="application/x-tex">I_1</annotation></semantics></math>
in the list.</p></li>
</ol>
<p>Together, those cells select a pair of reviewers from the list,
ensuring the same reviewer is not selected twice.</p>
</section>
<section id="mapping-to-applications" class="level2">
<h2>Mapping to Applications</h2>
<p>Back in the application tracker, we each application has an
application number, assigned when it is added to the spreadsheet and
never changed. We then look up the assignment for that application
number, and include it in the sheet. To do this in “wide” format, where
each reviewer has a column that gets marked “X” for applications
assigned to them, we use the following formula (assuming the reviewer
column is <code>E</code>):</p>
<pre><code>=IF(OR(VLOOKUP([@Entry], RVRNG, 6, FALSE)=E$1,
       VLOOKUP([@Entry], RVRNG, 7, FALSE)=E$1),
    &quot;X&quot;, &quot;&quot;)</code></pre>
</section>
<section id="final-remarks" class="level2">
<h2>Final Remarks</h2>
<p>I’ve been through a couple of iterations to get to this point, but it
seems to be a pretty robust and solid way to assign reviewers within
Excel, without needing to import assignments from other tools. So long
as application numbers are not changed, assignments are stable as the
application table is sorted, cells are changed (e.g., replacing the “X”
with reviewer comments), and most other manipulations.</p>
<p>Hope this is helpful! The Excel sheet is available <a
href="https://view.officeapps.live.com/op/view.aspx?src=https%3A%2F%2Fmd.ekstrandom.net%2Ffiles%2Fresources%2FReviewerAssignmentDemo.xlsx">here</a>
(<a href="/files/resources/ReviewerAssignmentDemo.xlsx">local
download</a>).</p>
</section>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Biased Lift for Related-Item Recommendation]]></title>
        <id>https://md.ekstrandom.net/blog/2025/01/biased-lift</id>
        <link href="https://md.ekstrandom.net/blog/2025/01/biased-lift"/>
        <updated>2025-01-26T05:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="compact right autosize">
<a href="/images/unsplash-lift.jpg"><img srcset="/images/unsplash-lift-320.jpg 1x, /images/unsplash-lift-480.jpg 1.5x, /images/unsplash-lift-640.jpg 2x" src="/images/unsplash-lift-640.jpg" alt="A man lifting a barbell with weights."></a>
<figcaption class="credit">
Photo by
<a href="https://unsplash.com/@victorfreitas?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Victor
Freitas</a> on
<a href="https://unsplash.com/photos/person-about-to-lift-barbell-At-NdsOf1jg?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a>.
</figcaption>
</figure>
<p>Effectively computing non-personalized recommendations can be
annoyingly subtle. If we do naïve things like sorting by average rating,
we get a top-<em>N</em> list dominated by items that one user really
liked. Sorting by overall popularity doesn’t have this problem; as soon
as we want contextual related-product recommendations, however
(e.g. “people who bought this also bought”), and don’t want those
recommendations to be dominated by the most popular items overall, the
problem comes roaring back.</p>
<section id="preliminaries" class="level2">
<h2>Preliminaries</h2>
<p>I’m going to use my <a href="/pubs/notation">usual notation</a> in
this blog post: we have a set
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>U</mi><annotation encoding="application/x-tex">U</annotation></semantics></math>
of
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>M</mi><annotation encoding="application/x-tex">M</annotation></semantics></math>
users who interact with
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>N</mi><annotation encoding="application/x-tex">N</annotation></semantics></math>
items forming set
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>I</mi><annotation encoding="application/x-tex">I</annotation></semantics></math>.</p>
<p>For convenience, let
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>m</mi><mi>i</mi></msub><mo>=</mo><mo stretchy="false" form="prefix">|</mo><msub><mi>U</mi><mi>i</mi></msub><mo stretchy="false" form="prefix">|</mo></mrow><annotation encoding="application/x-tex">m_i = |U_i|</annotation></semantics></math>
be the number of users who have interacted with item
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>i</mi><mo>∈</mo><mi>I</mi></mrow><annotation encoding="application/x-tex">i \in I</annotation></semantics></math>.
We can directly sort by this value, in descending order, to find the
<em>most popular</em> items (those items who have been interacted with
by the most users). We can also frame overall popularity as the
probability that a user will interact with that item (assuming, for the
moment, that all users are equally likely or representative of future
incoming users):</p>
<p><math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi mathvariant="normal">P</mi><mo>&#8289;</mo></mrow><mo stretchy="false" form="prefix">[</mo><mi>i</mi><mo stretchy="false" form="postfix">]</mo><mo>=</mo><msub><mrow><mi mathvariant="normal">P</mi><mo>&#8289;</mo></mrow><mrow><mi>u</mi><mo>∈</mo><mi>U</mi></mrow></msub><mo stretchy="false" form="prefix">[</mo><mi>i</mi><mo>∈</mo><msub><mi>I</mi><mi>u</mi></msub><mo stretchy="false" form="postfix">]</mo><mo>=</mo><mfrac><msub><mi>m</mi><mi>i</mi></msub><mi>M</mi></mfrac></mrow><annotation encoding="application/x-tex">\operatorname{P}[i] = \operatorname{P}_{u \in U}[i \in I_u] = \frac{m_i}{M}</annotation></semantics></math></p>
<p>When we have explicit ratings, user
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>u</mi><annotation encoding="application/x-tex">u</annotation></semantics></math>
gives item
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>i</mi><annotation encoding="application/x-tex">i</annotation></semantics></math>
the rating
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><msub><mi>r</mi><mrow><mi>u</mi><mi>i</mi></mrow></msub><annotation encoding="application/x-tex">r_{ui}</annotation></semantics></math>,
and the average rating for an item is
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><msub><mover><mi>r</mi><mo accent="true">‾</mo></mover><mrow><mi>u</mi><mi>i</mi></mrow></msub><annotation encoding="application/x-tex">\bar{r}_{ui}</annotation></semantics></math>.</p>
<p>To recommend items, we compute some score
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi><mo stretchy="false" form="prefix">(</mo><mi>i</mi><mo stretchy="false" form="prefix">|</mo><mi>…</mi><mo stretchy="false" form="postfix">)</mo></mrow><annotation encoding="application/x-tex">s(i|\dots)</annotation></semantics></math>
and pick the items with the highest score, or use these scors in some
more sophisticated ranker such as a sampler or diversifier.</p>
</section>
<section id="related-products" class="level2">
<h2>Related Products</h2>
<p>If we want to move beyond overall popularity to <em>contextual</em>
probability, where the context is a reference item
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>j</mi><annotation encoding="application/x-tex">j</annotation></semantics></math>
(e.g., the item the user is looking at right now), the first thing we
can try is just the conditional probability:</p>
<p><math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi><mo stretchy="false" form="prefix">(</mo><mi>i</mi><mo stretchy="false" form="prefix">|</mo><mi>j</mi><mo stretchy="false" form="postfix">)</mo><mo>=</mo><mrow><mi mathvariant="normal">P</mi><mo>&#8289;</mo></mrow><mo stretchy="false" form="prefix">[</mo><mi>i</mi><mo stretchy="false" form="prefix">|</mo><mi>j</mi><mo stretchy="false" form="postfix">]</mo><mo>=</mo><mfrac><msub><mi>m</mi><mrow><mi>i</mi><mi>j</mi></mrow></msub><msub><mi>m</mi><mi>j</mi></msub></mfrac></mrow><annotation encoding="application/x-tex">s(i|j) = \operatorname{P}[i|j] = \frac{m_{ij}}{m_j}</annotation></semantics></math></p>
<p>This is convenient and straightforward: for some candidate item
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>i</mi><annotation encoding="application/x-tex">i</annotation></semantics></math>,
what is the probability the user will interact with
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>i</mi><annotation encoding="application/x-tex">i</annotation></semantics></math>
given that they have interacted with
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>j</mi><annotation encoding="application/x-tex">j</annotation></semantics></math>?
We can then use those probabilities to rank the recommendations. Joe
Konstan and I taught this in the early videos in the Coursera
Recommender Systems MOOC, and I keep teaching it in my in-person
recommender systems classes today.</p>
<p>Conditional probability has an interesting problem, however: what if
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>i</mi><annotation encoding="application/x-tex">i</annotation></semantics></math>
is very popular? If a candidate item is highly popular, it will have a
high conditional probability regardless of the reference item. We
sometimes call this the “banana problem”: lots of people buy bananas, so
for any other item in the grocery store, there’s a reasonably good
probability the user will also buy bananas. Further, most people already
know about bananas, so recommending bananas is less likely to give them
new information than recommending some less-common item that frequently
co-occurs with the reference item.</p>
<p>One way to address this — and the method we taught in the MOOC — is
to use <a
href="https://en.wikipedia.org/wiki/Lift_(data_mining)">lift</a>:</p>
<p><math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mtable><mtr><mtd columnalign="right" style="text-align: right; padding-right: 0"><mi>s</mi><mo stretchy="false" form="prefix">(</mo><mi>i</mi><mo stretchy="false" form="prefix">|</mo><mi>j</mi><mo stretchy="false" form="postfix">)</mo><mo>=</mo><mrow><mi mathvariant="normal">Lift</mi><mo>&#8289;</mo></mrow><mo stretchy="false" form="prefix">(</mo><mi>i</mi><mo>,</mo><mi>j</mi><mo stretchy="false" form="postfix">)</mo></mtd><mtd columnalign="left" style="text-align: left; padding-left: 0"><mo>=</mo><mfrac><mrow><mrow><mi mathvariant="normal">P</mi><mo>&#8289;</mo></mrow><mo stretchy="false" form="prefix">[</mo><mi>i</mi><mo>,</mo><mi>j</mi><mo stretchy="false" form="postfix">]</mo></mrow><mrow><mrow><mi mathvariant="normal">P</mi><mo>&#8289;</mo></mrow><mo stretchy="false" form="prefix">[</mo><mi>i</mi><mo stretchy="false" form="postfix">]</mo><mrow><mi mathvariant="normal">P</mi><mo>&#8289;</mo></mrow><mo stretchy="false" form="prefix">[</mo><mi>j</mi><mo stretchy="false" form="postfix">]</mo></mrow></mfrac></mtd></mtr><mtr><mtd columnalign="right" style="text-align: right; padding-right: 0"></mtd><mtd columnalign="left" style="text-align: left; padding-left: 0"><mo>=</mo><mi>M</mi><mfrac><msub><mi>m</mi><mrow><mi>i</mi><mi>j</mi></mrow></msub><mrow><msub><mi>m</mi><mi>i</mi></msub><msub><mi>m</mi><mi>j</mi></msub></mrow></mfrac></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{align*}
s(i|j) = \operatorname{Lift}(i,j) &amp; = \frac{\operatorname{P}[i,j]}{\operatorname{P}[i]\operatorname{P}[j]} \\
&amp; = M \frac{m_{ij}}{m_i m_j}
\end{align*}</annotation></semantics></math></p>
<p>Lift measures the <em>degree of statistical relatedness</em> of two
events. If the two events (items, in our case) are independent, then
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi mathvariant="normal">P</mi><mo>&#8289;</mo></mrow><mo stretchy="false" form="prefix">[</mo><mi>i</mi><mo>,</mo><mi>j</mi><mo stretchy="false" form="postfix">]</mo><mo>=</mo><mrow><mi mathvariant="normal">P</mi><mo>&#8289;</mo></mrow><mo stretchy="false" form="prefix">[</mo><mi>i</mi><mo stretchy="false" form="postfix">]</mo><mrow><mi mathvariant="normal">P</mi><mo>&#8289;</mo></mrow><mo stretchy="false" form="prefix">[</mo><mi>j</mi><mo stretchy="false" form="postfix">]</mo></mrow><annotation encoding="application/x-tex">\operatorname{P}[i,j] = \operatorname{P}[i]\operatorname{P}[j]</annotation></semantics></math>,
and
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi mathvariant="normal">Lift</mi><mo>&#8289;</mo></mrow><mo stretchy="false" form="prefix">(</mo><mi>i</mi><mo>,</mo><mi>j</mi><mo stretchy="false" form="postfix">)</mo><mo>=</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">\operatorname{Lift}(i,j)=1</annotation></semantics></math>.
Higher lift values arise when the items co-occur more frequently than we
would expect by chance if the items were independent, indicating that
they are statistically related (lower values arise when they co-occur
less frequently). If we want lift scores to be better-behaved
numerically, perhaps to combine with other scores in a hybrid model, we
can use log lift, but this doesn’t change the ordering of
potentially-related items.</p>
<p>Lift has a different problem, though: what if
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>i</mi><annotation encoding="application/x-tex">i</annotation></semantics></math>
is very unpopular, and therefore
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><msub><mi>m</mi><mi>i</mi></msub><annotation encoding="application/x-tex">m_i</annotation></semantics></math>
and
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi mathvariant="normal">P</mi><mo>&#8289;</mo></mrow><mo stretchy="false" form="prefix">[</mo><mi>i</mi><mo stretchy="false" form="postfix">]</mo></mrow><annotation encoding="application/x-tex">\operatorname{P}[i]</annotation></semantics></math>
are small? Perhaps it is so unpopular, that its only interactions
co-occur with interactions with
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>j</mi><annotation encoding="application/x-tex">j</annotation></semantics></math>?
In this case, lift will be very high, but has low support: we are making
very confident conclusions of item relatedless on the basis of a small
number of interactions (sometimes only a single interaction).</p>
<p>What can we do about this?</p>
</section>
<section id="sidebar-bias" class="level2">
<h2>Sidebar: Bias</h2>
<p>The bias-variance tradeoff is our key to solving this problem. To see
how that might work, let’s go to a different model that we also taught
in the MOOC and discussed in our <a href="/pubs/cf-survey">old
survey</a>, and I picked up in turn from <a
href="https://sifter.org/~simon/journal/20061211.html">FunkSVD</a>, I
think: the <em>bias model</em>.</p>
<p>If we want to compute the average rating for an item, the basic way
to do this is with a simple mean:</p>
<p><math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mover><mi>r</mi><mo accent="true">‾</mo></mover><mi>i</mi></msub><mo>=</mo><mfrac><mn>1</mn><mrow><mo stretchy="false" form="prefix">|</mo><msub><mi>U</mi><mi>i</mi></msub><mo stretchy="false" form="prefix">|</mo></mrow></mfrac><munder><mo>∑</mo><mrow><mi>u</mi><mo>∈</mo><msub><mi>U</mi><mi>i</mi></msub></mrow></munder><msub><mi>r</mi><mrow><mi>u</mi><mi>i</mi></mrow></msub></mrow><annotation encoding="application/x-tex">\bar{r}_i = \frac{1}{|U_i|} \sum_{u \in U_i} r_{ui}</annotation></semantics></math></p>
<p>The mean has a similar problem as lift — if one user <em>really</em>
liked a movie and rated it 5 stars, it will have an average of 5,
beating out movies that a lot of people rated and gave a respectable
average of 4.7 over, say, 10K ratings.</p>
<p>An easy fix for this problem is to introduce some <em>damping</em> or
<em>bias</em> based on the global average rating<a href="#fn1"
class="footnote-ref" id="fnref1"
role="doc-noteref"><sup>1</sup></a>:</p>
<p><math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mover><mover><mi>r</mi><mo accent="true">‾</mo></mover><mo accent="true">̇</mo></mover><mi>i</mi></msub><mo>=</mo><mfrac><mrow><mrow><mo stretchy="true" form="prefix">(</mo><munder><mo>∑</mo><mrow><mi>u</mi><mo>∈</mo><msub><mi>U</mi><mi>i</mi></msub></mrow></munder><msub><mi>r</mi><mrow><mi>u</mi><mi>i</mi></mrow></msub><mo stretchy="true" form="postfix">)</mo></mrow><mo>+</mo><mi>γ</mi><mover><mi>r</mi><mo accent="true">‾</mo></mover></mrow><mrow><mo stretchy="false" form="prefix">|</mo><msub><mi>U</mi><mi>i</mi></msub><mo stretchy="false" form="prefix">|</mo><mi>+</mi><mi>γ</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">\dot{\bar{r}}_i = \frac{\left(\sum_{u \in U_i} r_{ui}\right) + \gamma \bar{r}}{|U_i| + \gamma}</annotation></semantics></math></p>
<p>There are at least two ways to think about this new term
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>γ</mi><annotation encoding="application/x-tex">\gamma</annotation></semantics></math>:</p>
<ul>
<li><em>A priori</em>, we start each item out with
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>γ</mi><annotation encoding="application/x-tex">\gamma</annotation></semantics></math>
virtual ratings equal to the global mean rating. This reflects a prior
assumption that, absent infromation about user preference, items are
probably average. As the item accrues more real ratings, those dominate
the virtual ratings and the damped or biased mean converges towards the
empirical mean. This is conceptually similar to Laplace smoothing for
naïve Bayesian modeling.</li>
<li>If you push through the calculus, this turns out to be equivalent to
computing the posterior expected value of Bayesian inference for the
item mean rating with an emirical prior and a Gaussian likelihood
function. Specifically, the prior for the inference is a Gaussian
distribution with an empirical mean
(<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>μ</mi><mn>0</mn></msub><mo>=</mo><mover><mi>r</mi><mo accent="true">‾</mo></mover></mrow><annotation encoding="application/x-tex">\mu_0 = \bar{r}</annotation></semantics></math>)
and variance derived from the damping strength
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>γ</mi><annotation encoding="application/x-tex">\gamma</annotation></semantics></math>.</li>
</ul>
<p>Either way we look at it, the result is to bias the mean, decreasing
its variance (in the bias-variance sense) and decreasing its
susceptibility to deriving high scores from little information.</p>
<p>The <strong>bias model</strong> provides another more flexible
mechanism to introduce this bias that has the benefit of also accounting
for users’ differing use of the rating scale. We can learn
<em>global</em>, <em>user</em>, and <em>item</em> biases and combine
them to compute a personalized mean:</p>
<p><math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mtable><mtr><mtd columnalign="right" style="text-align: right; padding-right: 0"><msub><mi>b</mi><mrow><mi>u</mi><mi>i</mi></mrow></msub></mtd><mtd columnalign="left" style="text-align: left; padding-left: 0"><mo>=</mo><msub><mi>b</mi><mn>0</mn></msub><mo>+</mo><msub><mi>b</mi><mi>i</mi></msub><mo>+</mo><msub><mi>b</mi><mi>u</mi></msub></mtd></mtr><mtr><mtd columnalign="right" style="text-align: right; padding-right: 0"><msub><mi>b</mi><mn>0</mn></msub></mtd><mtd columnalign="left" style="text-align: left; padding-left: 0"><mo>=</mo><mover><mi>r</mi><mo accent="true">‾</mo></mover><mo>=</mo><mfrac><mn>1</mn><mrow><mo stretchy="false" form="prefix">|</mo><mi>R</mi><mo stretchy="false" form="prefix">|</mo></mrow></mfrac><munder><mo>∑</mo><mrow><msub><mi>r</mi><mrow><mi>u</mi><mi>i</mi></mrow></msub><mo>∈</mo><mi>R</mi></mrow></munder><msub><mi>r</mi><mrow><mi>u</mi><mi>i</mi></mrow></msub></mtd></mtr><mtr><mtd columnalign="right" style="text-align: right; padding-right: 0"><msub><mi>b</mi><mi>i</mi></msub></mtd><mtd columnalign="left" style="text-align: left; padding-left: 0"><mo>=</mo><mfrac><mrow><munder><mo>∑</mo><mrow><mi>u</mi><mo>∈</mo><msub><mi>U</mi><mi>i</mi></msub></mrow></munder><mo stretchy="false" form="prefix">(</mo><msub><mi>r</mi><mrow><mi>u</mi><mi>i</mi></mrow></msub><mo>−</mo><msub><mi>b</mi><mn>0</mn></msub><mo stretchy="false" form="postfix">)</mo></mrow><mrow><mo stretchy="false" form="prefix">|</mo><msub><mi>U</mi><mi>i</mi></msub><mo stretchy="false" form="prefix">|</mo><mi>+</mi><mi>α</mi></mrow></mfrac></mtd></mtr><mtr><mtd columnalign="right" style="text-align: right; padding-right: 0"><msub><mi>b</mi><mi>u</mi></msub></mtd><mtd columnalign="left" style="text-align: left; padding-left: 0"><mo>=</mo><mfrac><mrow><munder><mo>∑</mo><mrow><mi>i</mi><mo>∈</mo><msub><mi>I</mi><mi>u</mi></msub></mrow></munder><mo stretchy="false" form="prefix">(</mo><msub><mi>r</mi><mrow><mi>u</mi><mi>i</mi></mrow></msub><mo>−</mo><msub><mi>b</mi><mn>0</mn></msub><mo>−</mo><msub><mi>b</mi><mi>i</mi></msub><mo stretchy="false" form="postfix">)</mo></mrow><mrow><mo stretchy="false" form="prefix">|</mo><msub><mi>I</mi><mi>u</mi></msub><mo stretchy="false" form="prefix">|</mo><mi>+</mi><mi>β</mi></mrow></mfrac></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{align*}
b_{ui} &amp; = b_0 + b_i + b_u \\
b_0 &amp; = \bar{r} = \frac{1}{|R|} \sum_{r_{ui} \in R} r_{ui} \\
b_i &amp; = \frac{\sum_{u \in U_i} (r_{ui} - b_0)}{|U_i| + \alpha} \\
b_u &amp; = \frac{\sum_{i \in I_u} (r_{ui} - b_0 - b_i)}{|I_u| + \beta}
\end{align*}</annotation></semantics></math></p>
<p>In this version, we center the data at each step, computing the item
and user biases from the residuals of the previous step: the item bias
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><msub><mi>b</mi><mi>i</mi></msub><annotation encoding="application/x-tex">b_i</annotation></semantics></math>
captures how much better or worse this item is than the global average
rating across all items, and the user bias
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><msub><mi>b</mi><mi>u</mi></msub><annotation encoding="application/x-tex">b_u</annotation></semantics></math>
captures how much more positive or negative this user is than
average.</p>
<p>We further bias these biases towards 0 with bias damping factors
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>α</mi><annotation encoding="application/x-tex">\alpha</annotation></semantics></math>
and
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>β</mi><annotation encoding="application/x-tex">\beta</annotation></semantics></math>.
Since the item and user biases are computed from mean-centered data, 0
is now a neutral value, so we can apply the damping factor only in the
denominator and obtain our biased biases that require more ratings to
learn a high estimate of an item’s quality.</p>
<p>The bias model also yields a third interpretation of the damping or
bias: while the precise parameter values might differ slightly due to
the step-by-step computation above, the resulting model is very similar
to what we would learn if we treated rating prediction as a ridge
regression problem with user and item IDs as categorical variables. I
sometimes do that, particularly when using the bias model as one
component of a more sophisticated regularized scoring model.</p>
<p>The point of all of this for our original problem is that we can
decrease the problematic behavior of our scores with respect to
low-information items by biasing the scores. We can implement this
biasing by starting each item out with “virtual interactions” expressing
neutral preference. The more virtual interactions we supply, the more
empirical interactions are needed to support a high score.</p>
</section>
<section id="biasing-lift" class="level2">
<h2>Biasing Lift</h2>
<p>This raises a question: how do we apply the same principle to
probabilities and lift? What would it mean to start each item out with
some number of “neutral” interactions for the purpose of lift?<a
href="#fn2" class="footnote-ref" id="fnref2"
role="doc-noteref"><sup>2</sup></a></p>
<p>Working this out is a little less obvious than starting each item
with some neutral ratings. Both because we need to define “neutral”, and
because there will be some additional terms involved (the number of
neutral interactions per item is not sufficient for the math to work
out).</p>
<p>Let’s define “neutral” as “independent”: our goal is for each item to
start out with
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>κ</mi><annotation encoding="application/x-tex">\kappa</annotation></semantics></math>
virtual interactions that are independent of any other item’s virtual
interactions. To compute probabilities, we also need to know the number
of virtual users
(<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>K</mi><annotation encoding="application/x-tex">K</annotation></semantics></math>).
We can set this directly, but it is more natural to derive it from
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><msub><mi>n</mi><mi>K</mi></msub><annotation encoding="application/x-tex">n_K</annotation></semantics></math>,
the (average) number of interactions supplied by each virtual user.
We’ll see at the end that we can actually ignore
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><msub><mi>n</mi><mi>K</mi></msub><annotation encoding="application/x-tex">n_K</annotation></semantics></math>
and
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>K</mi><annotation encoding="application/x-tex">K</annotation></semantics></math>
and derive a sufficient approximation of biased lift with only
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>κ</mi><annotation encoding="application/x-tex">\kappa</annotation></semantics></math>,
but we’ll use them for now.</p>
<p>If each item has
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>κ</mi><annotation encoding="application/x-tex">\kappa</annotation></semantics></math>
virtual interactions, we need
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>κ</mi><mi>N</mi></mrow><annotation encoding="application/x-tex">\kappa N</annotation></semantics></math>
total virtual interactions. If each virtual user supplies
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><msub><mi>n</mi><mi>K</mi></msub><annotation encoding="application/x-tex">n_K</annotation></semantics></math>
virtual independent interactions, then the virtual user count
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>K</mi><mo>=</mo><mfrac><mrow><mi>κ</mi><mi>N</mi></mrow><msub><mi>n</mi><mi>K</mi></msub></mfrac></mrow><annotation encoding="application/x-tex">K = \frac{\kappa N}{n_K}</annotation></semantics></math>.
With these in hand, we can define the prior probability of an item, or
the probability that one of our virtual users will interact with it:</p>
<p><math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mrow><mi mathvariant="normal">P</mi><mo>&#8289;</mo></mrow><mi>K</mi></msub><mo stretchy="false" form="prefix">[</mo><mi>i</mi><mo stretchy="false" form="postfix">]</mo><mo>=</mo><mfrac><mi>κ</mi><mi>K</mi></mfrac></mrow><annotation encoding="application/x-tex">
\operatorname{P}_K[i] = \frac{\kappa}{K}
</annotation></semantics></math></p>
<p>Since all virtual interactions are independent, we can define the
prior joint probability of individual item pairs:</p>
<p><math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mrow><mi mathvariant="normal">P</mi><mo>&#8289;</mo></mrow><mi>K</mi></msub><mo stretchy="false" form="prefix">[</mo><mi>i</mi><mo>,</mo><mi>j</mi><mo stretchy="false" form="postfix">]</mo><mo>=</mo><msub><mrow><mi mathvariant="normal">P</mi><mo>&#8289;</mo></mrow><mi>K</mi></msub><mo stretchy="false" form="prefix">[</mo><mi>i</mi><mo stretchy="false" form="postfix">]</mo><msub><mrow><mi mathvariant="normal">P</mi><mo>&#8289;</mo></mrow><mi>K</mi></msub><mo stretchy="false" form="prefix">[</mo><mi>j</mi><mo stretchy="false" form="postfix">]</mo><mo>=</mo><mfrac><msup><mi>κ</mi><mn>2</mn></msup><msup><mi>K</mi><mn>2</mn></msup></mfrac></mrow><annotation encoding="application/x-tex">
\operatorname{P}_K[i,j] = \operatorname{P}_K[i] \operatorname{P}_K[j] = \frac{\kappa^2}{K^2}
</annotation></semantics></math></p>
<p>To define a biased probability
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mover><mi mathvariant="normal">P</mi><mo accent="true">̇</mo></mover><mo stretchy="false" form="prefix">[</mo><mi>i</mi><mo stretchy="false" form="postfix">]</mo></mrow><annotation encoding="application/x-tex">\dot{\operatorname{P}}[i]</annotation></semantics></math>
that incorporates our virtual clicks, an item with
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><msub><mi>m</mi><mi>i</mi></msub><annotation encoding="application/x-tex">m_i</annotation></semantics></math>
real clicks will have an additional
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>κ</mi><annotation encoding="application/x-tex">\kappa</annotation></semantics></math>
virtual clicks from a pool of
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>K</mi><annotation encoding="application/x-tex">K</annotation></semantics></math>
virtual users. This yields:</p>
<p><math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mover><mi mathvariant="normal">P</mi><mo accent="true">̇</mo></mover><mo stretchy="false" form="prefix">[</mo><mi>i</mi><mo stretchy="false" form="postfix">]</mo><mo>=</mo><mfrac><mrow><msub><mi>m</mi><mi>i</mi></msub><mo>+</mo><mi>κ</mi></mrow><mrow><mi>M</mi><mo>+</mo><mi>K</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">
\dot{\operatorname{P}}[i] = \frac{m_i + \kappa}{M + K}
</annotation></semantics></math></p>
<p>Next, we will define the biased joint probability
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mover><mi mathvariant="normal">P</mi><mo accent="true">̇</mo></mover><mo stretchy="false" form="prefix">[</mo><mi>i</mi><mo>,</mo><mi>j</mi><mo stretchy="false" form="postfix">]</mo></mrow><annotation encoding="application/x-tex">\dot{\operatorname{P}}[i,j]</annotation></semantics></math>.
For this, we need to know expected number of virtual co-occurrences a
pair of items will have: of our
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>K</mi><annotation encoding="application/x-tex">K</annotation></semantics></math>
virtual users, how many will have interacted with both items? This is
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>K</mi><msub><mrow><mi mathvariant="normal">P</mi><mo>&#8289;</mo></mrow><mi>K</mi></msub><mo stretchy="false" form="prefix">[</mo><mi>i</mi><mo>,</mo><mi>j</mi><mo stretchy="false" form="postfix">]</mo><mo>=</mo><mfrac><msup><mi>κ</mi><mn>2</mn></msup><mi>K</mi></mfrac></mrow><annotation encoding="application/x-tex">K \operatorname{P}_K[i,j] = \frac{\kappa^2}{K}</annotation></semantics></math>,
allowing us to compute the biased joint probability:</p>
<p><math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mover><mi mathvariant="normal">P</mi><mo accent="true">̇</mo></mover><mo stretchy="false" form="prefix">[</mo><mi>i</mi><mo>,</mo><mi>j</mi><mo stretchy="false" form="postfix">]</mo><mo>=</mo><mfrac><mrow><msub><mi>m</mi><mrow><mi>i</mi><mi>j</mi></mrow></msub><mo>+</mo><mfrac><msup><mi>κ</mi><mn>2</mn></msup><mi>K</mi></mfrac></mrow><mrow><mi>M</mi><mo>+</mo><mi>K</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">
\dot{\operatorname{P}}[i,j] = \frac{m_{ij} + \frac{\kappa^2}{K}}{M + K}
</annotation></semantics></math></p>
<p>We can plug these biased joint and unconditinoal probabilities into
the lift formula to get biased lift:</p>
<p><math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mtable><mtr><mtd columnalign="right" style="text-align: right; padding-right: 0"><mrow><mi mathvariant="normal">BiasedLift</mi><mo>&#8289;</mo></mrow><mo stretchy="false" form="prefix">[</mo><mi>i</mi><mo>,</mo><mi>j</mi><mo stretchy="false" form="postfix">]</mo></mtd><mtd columnalign="left" style="text-align: left; padding-left: 0"><mo>=</mo><mfrac><mrow><mover><mi mathvariant="normal">P</mi><mo accent="true">̇</mo></mover><mo stretchy="false" form="prefix">[</mo><mi>i</mi><mo>,</mo><mi>j</mi><mo stretchy="false" form="postfix">]</mo></mrow><mrow><mover><mi mathvariant="normal">P</mi><mo accent="true">̇</mo></mover><mo stretchy="false" form="prefix">[</mo><mi>i</mi><mo stretchy="false" form="postfix">]</mo><mover><mi mathvariant="normal">P</mi><mo accent="true">̇</mo></mover><mo stretchy="false" form="prefix">[</mo><mi>j</mi><mo stretchy="false" form="postfix">]</mo></mrow></mfrac></mtd></mtr><mtr><mtd columnalign="right" style="text-align: right; padding-right: 0"></mtd><mtd columnalign="left" style="text-align: left; padding-left: 0"><mo>=</mo><mfrac><mrow><msub><mi>m</mi><mrow><mi>i</mi><mi>j</mi></mrow></msub><mo>+</mo><mfrac><msup><mi>κ</mi><mn>2</mn></msup><mi>K</mi></mfrac></mrow><mrow><mi>M</mi><mo>+</mo><mi>K</mi></mrow></mfrac><mo>⋅</mo><mfrac><mrow><mi>M</mi><mo>+</mo><mi>K</mi></mrow><mrow><msub><mi>m</mi><mi>i</mi></msub><mo>+</mo><mi>κ</mi></mrow></mfrac><mo>⋅</mo><mfrac><mrow><mi>M</mi><mo>+</mo><mi>K</mi></mrow><mrow><msub><mi>m</mi><mi>j</mi></msub><mo>+</mo><mi>κ</mi></mrow></mfrac></mtd></mtr><mtr><mtd columnalign="right" style="text-align: right; padding-right: 0"></mtd><mtd columnalign="left" style="text-align: left; padding-left: 0"><mo>=</mo><mo stretchy="false" form="prefix">(</mo><mi>M</mi><mo>+</mo><mi>K</mi><mo stretchy="false" form="postfix">)</mo><mfrac><mrow><msub><mi>m</mi><mrow><mi>i</mi><mi>j</mi></mrow></msub><mo>+</mo><mfrac><msup><mi>κ</mi><mn>2</mn></msup><mi>K</mi></mfrac></mrow><mrow><mo stretchy="false" form="prefix">(</mo><msub><mi>m</mi><mi>i</mi></msub><mo>+</mo><mi>κ</mi><mo stretchy="false" form="postfix">)</mo><mo stretchy="false" form="prefix">(</mo><msub><mi>m</mi><mi>j</mi></msub><mo>+</mo><mi>κ</mi><mo stretchy="false" form="postfix">)</mo></mrow></mfrac></mtd></mtr><mtr><mtd columnalign="right" style="text-align: right; padding-right: 0"></mtd><mtd columnalign="left" style="text-align: left; padding-left: 0"><mo>≈</mo><mi>M</mi><mfrac><msub><mi>m</mi><mrow><mi>i</mi><mi>j</mi></mrow></msub><mrow><mo stretchy="false" form="prefix">(</mo><msub><mi>m</mi><mi>i</mi></msub><mo>+</mo><mi>κ</mi><mo stretchy="false" form="postfix">)</mo><mo stretchy="false" form="prefix">(</mo><msub><mi>m</mi><mi>j</mi></msub><mo>+</mo><mi>κ</mi><mo stretchy="false" form="postfix">)</mo></mrow></mfrac></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{align*}
\operatorname{BiasedLift}[i,j] &amp; = \frac{\dot{\operatorname{P}}[i,j]}{\dot{\operatorname{P}}[i]\dot{\operatorname{P}}[j]} \\
&amp; = \frac{m_{ij} + \frac{\kappa^2}{K}}{M + K}
\cdot \frac{M+K}{m_i + \kappa}
\cdot \frac{M+K}{m_j + \kappa} \\
&amp; = (M + K) \frac{m_{ij} + \frac{\kappa^2}{K}}{(m_i + \kappa)(m_j + \kappa)} \\
&amp; \approx M \frac{m_{ij}}{(m_i + \kappa)(m_j + \kappa)}
\end{align*}</annotation></semantics></math></p>
<p>The last simplification is not a precise computation of the biased
lift, but can be used to identify the items pairs with the highest lift.
We selected
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>κ</mi><annotation encoding="application/x-tex">\kappa</annotation></semantics></math>
to be a constant, and
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>K</mi><annotation encoding="application/x-tex">K</annotation></semantics></math>
is fixed given the data set (since it is computed from fixed
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>N</mi><annotation encoding="application/x-tex">N</annotation></semantics></math>
and the constants
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>κ</mi><annotation encoding="application/x-tex">\kappa</annotation></semantics></math>
and
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><msub><mi>n</mi><mi>K</mi></msub><annotation encoding="application/x-tex">n_K</annotation></semantics></math>).
Therefore, adding
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>K</mi><annotation encoding="application/x-tex">K</annotation></semantics></math>
or terms involving only
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>K</mi><annotation encoding="application/x-tex">K</annotation></semantics></math>
and
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>κ</mi><annotation encoding="application/x-tex">\kappa</annotation></semantics></math>
do change the order of item pairs within a single data set.</p>
<p>Further,
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>K</mi><mo>≫</mo><msup><mi>κ</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">K \gg \kappa^2</annotation></semantics></math>,
so adding
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mfrac><msup><mi>κ</mi><mn>2</mn></msup><mi>K</mi></mfrac><annotation encoding="application/x-tex">\frac{\kappa^2}{K}</annotation></semantics></math>
in the numerator is adding a very small quantity, and can be effectively
ignored for approximation purposes.</p>
<p>Since the final ordering score no longer depends on
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>K</mi><annotation encoding="application/x-tex">K</annotation></semantics></math>,
we actually do not need to pick
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><msub><mi>n</mi><mi>K</mi></msub><annotation encoding="application/x-tex">n_K</annotation></semantics></math>
if we just want to use biased lift to select the most related
recommendations for a reference item. We can select
<math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mi>κ</mi><annotation encoding="application/x-tex">\kappa</annotation></semantics></math>
(higher values increase the number of real co-occurrances needed to
obtain a high biased lift score), and sort by biased scores computed
directly from interaction counts:</p>
<p><math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi><mo stretchy="false" form="prefix">(</mo><mi>i</mi><mo stretchy="false" form="prefix">|</mo><mi>j</mi><mo stretchy="false" form="postfix">)</mo><mo>=</mo><mi>M</mi><mfrac><msub><mi>m</mi><mrow><mi>i</mi><mi>j</mi></mrow></msub><mrow><mo stretchy="false" form="prefix">(</mo><msub><mi>m</mi><mi>i</mi></msub><mo>+</mo><mi>κ</mi><mo stretchy="false" form="postfix">)</mo><mo stretchy="false" form="prefix">(</mo><msub><mi>m</mi><mi>j</mi></msub><mo>+</mo><mi>κ</mi><mo stretchy="false" form="postfix">)</mo></mrow></mfrac></mrow><annotation encoding="application/x-tex">
s(i|j) = M \frac{m_{ij}}{(m_i + \kappa)(m_j + \kappa)}
</annotation></semantics></math></p>
<p>And it works! I don’t know how <em>well</em> it works yet, overall,
but in my preliminary trials in class, it did exactly what I hoped,
computing related-book recommendations that weren’t dominated by either
most-popular-overall or
unpopular-but-that-one-user-also-read-the-reference-book
recommendations. I’ll probably add it to LensKit soon.</p>
</section>
<section id="footnotes" class="footnotes footnotes-end-of-document"
role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>We called this the <em>damped mean</em> in early writing
and teaching; <em>biased mean</em> is perhaps better, but may be
confusing with biases in the bias model coming in a couple of
paragraphs.<a href="#fnref1" class="footnote-back"
role="doc-backlink">↩︎</a></p></li>
<li id="fn2"><p>I have to believe others have asked this question, given
how old lift is as a metric, but I have been unable to find references
to damping or biasing lift in this way. If someone knows one, please
send it my way!<a href="#fnref2" class="footnote-back"
role="doc-backlink">↩︎</a></p></li>
</ol>
</section>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[2024 in Review]]></title>
        <id>https://md.ekstrandom.net/blog/2024/12/2024</id>
        <link href="https://md.ekstrandom.net/blog/2024/12/2024"/>
        <updated>2024-12-29T05:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="right autosize compact">
<a href="/images/2024.png"><img srcset="/images/2024-320.png 1x, /images/2024-480.png 1.5x, /images/2024-640.png 2x" src="/images/2024-640.png" alt="“2024” in green on a black background.
"></a>
</figure>
<p>In my end-of-year blogging, I usually provide an <a
href="/blog/2024/12/tools">update on my toolbox</a>, and a review of the
year.</p>
<p>So what’s happened? A quick (and likely incomplete) summary…</p>
<hr class="fold">
<ul>
<li>Taught recommender systems (DSCI 641) for the first time at
Drexel.</li>
<li>Taught 2 terms (spring and fall 2024) of INFO 659 (Intro to Data
Analytics), revising material and pacing in the second offering.</li>
<li>Proposed a new class for AY2025–2026 on <em>Data Workflow
Engineering</em> (reproducible, adaptable data science and analytics
workflows, model maintenance and monitoring, some ML Ops, etc.).</li>
<li>Proposed a Ph.D. seminar course, which I will teach in winter
quarter 2025, on fairness.</li>
<li>Recruited 2 Ph.D. students.</li>
<li>Launched <a href="https://inertial.science">my new lab</a>.</li>
<li>Mostly finished a significant <a
href="https://lkpy.lenskit.org/en/latest/guide/migrating.html">LensKit
upgrade</a> to make it easier to use and extend.</li>
<li>Presented 3 papers at ECIR 2024.</li>
<li>Gave a <a href="/talks/2024/ir4u2">keynote</a> at the ECIR Workshop
on Information Retrieval for Understudied Users (IR4U2).</li>
<li>Gave a <a href="/talks/2024/roegen">keynote</a> at the RecSys
Workshop on Risks, Opportunities, and Evaluation of Generative Models in
Recommender Systems (ROEGEN).</li>
<li>Participated in a panel for the SIGIR workshop on LLMs for
Evaluation.</li>
<li>Gave seminar talks at <a href="/talks/2024/boulder">CU Boulder</a>
and <a href="/talks/2024/delft">TU Delft</a>.</li>
<li>Two papers in TORS went to publication (<a
href="/pubs/tors-distributions">distributional evaluation</a> and <a
href="/pubs/recsys-values">values</a>).</li>
<li>Published a <a href="/pubs/not-you-me">short paper</a> in RecSys
with Andrés Ferraro and Christine Bauer on gender bias in music
recommendation.</li>
<li>Co-organized the 7th FAccTRec workshop at RecSys 2024.</li>
<li>Co-organized the 1st AltRecSys workshop at RecSys 2024.</li>
<li>Gave a tutorial at RecSys 2024 on our <a
href="https://poprox.ai">POPROX news recommender platform</a>.</li>
<li>Contributed to significant progress on building out POPROX and
mamking it ready for research.</li>
<li>Submitted 4 grant proposals, and made significant progress on a
5th.</li>
<li>Submitted my first papers with Drexel Ph.D. students.</li>
<li>Co-authored a paper in the HEAL workshop at CHI on <a
href="/pubs/heal-dubious">LLM fairness</a>.</li>
<li>Joined Fernando Diaz and Bhaskar Mitra on another paper currently
under review.</li>
<li>Served on the department’s 2023–2024 faculty search committee.</li>
<li>Served on the department Ph.D. committee.</li>
<li>Joined my first Drexel Ph.D. comprehensive exam committees.</li>
<li>Generally settled in to Drexel.</li>
<li>Wrote <a href="https://github.com/mdekstrand/yafsm">yet another fine
system monitor</a>.</li>
<li>Set up my lab’s equipment (with help from Drexel CCI IT) to measure
power. consumption of recommender systems experiments.</li>
<li>Bought a house in Philadelphia (and moved into it).</li>
<li>Probably finished losing my religion.</li>
<li>Set up a lot of new hardware at both work and home.</li>
</ul>
<p>It’s been a year!</p>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[2024 State of the Tools]]></title>
        <id>https://md.ekstrandom.net/blog/2024/12/tools</id>
        <link href="https://md.ekstrandom.net/blog/2024/12/tools"/>
        <updated>2024-12-22T05:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="right compact autosize">
<a href="/images/unsplash-tools-2024.jpg"><img srcset="/images/unsplash-tools-2024-320.jpg 1x, /images/unsplash-tools-2024-480.jpg 1.5x, /images/unsplash-tools-2024-640.jpg 2x" src="/images/unsplash-tools-2024-640.jpg" alt="Old wookdworking tools hanging on a wall."></a>
<figcaption class="credit">
Photo by
<a href="https://unsplash.com/@pjswinburn?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Philip
Swinburn</a> on
<a href="https://unsplash.com/photos/carving-tool-set-vS7LVkPyXJU?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a>.
</figcaption>
</figure>
<p>It’s that time of year again! Since I did a relatively full reset <a
href="/blog/2023/12/tools">last year</a>, for this year’s State of the
Tools I’ll focus on changes and a few highlights. My <a
href="/blog/2024/01/zoom-lecture-studio">Zoom studio post</a> discusses
my hardware and software for video recording in more detail.</p>
<hr class="fold">
<section id="hardware" class="level2">
<h2>Hardware</h2>
<p>My core hardware is the same as year; there are a few tweaks or
additions:</p>
<ul>
<li><p>Bought a modern gaming rig at work for ML development and testing
to increase my lab’s compute capacity and allow me to work on
recommender software development while the datacenter machine is busy
with bigger jobs and student work. It’s an HP Omen 45L with an i9 14900K
and GeForce RTX 4090. After the first of the year, I will also be adding
a <a href="https://geekworm.com/products/pikvm-x650">Geekworm PiKVM</a>
for remote management.</p></li>
<li><p>The RockPro64 board in my home NAS died, so I replaced it with a
cheap NAS/HTPC machine built on the Intel N100 chipset (<a
href="https://a.co/d/hjiRMIv">link</a>). It’s been working great. I also
added a Sabrent 4-bay USB 3.2 drive enclosure with a couple of 16TB
drives to expand my data storage; now that it’s Intel-based, I switched
the main file system from XFS back to (mirrored) ZFS for increased
flexibility with external storage expansion.</p></li>
<li><p>Bought a Steam Deck for gaming (and software testing).</p></li>
<li><p>Bought a 144TB 8-bay Thunderbolt RAID for university data
archival and slow storage.</p></li>
</ul>
<p>I’ve also been working with Drexel CCI IT to set up the ability to
monitor power consumption for my work compute resources; I’ll write more
about the details of that another time.</p>
</section>
<section id="productivity-and-work-management" class="level2">
<h2>Productivity and Work Management</h2>
<p>After experimenting with Logseq for a few months, I have switched to
<a href="https://obsidian.md/">Obsidian</a> for project and task
tracking. Logseq’s data model might be a better fit for what I need, but
the lack of good documentation for its query language was increasingly
frustrating. Obsidian lets me write my custom query and reporting logic
in plain JavaScript.</p>
</section>
<section id="operating-systems" class="level2">
<h2>Operating Systems</h2>
<p>Nothing new here; still macOS on endpoints, Debian on personal
servers, and Ubuntu (grudgingly) on work servers. I’ve got a Windows 11
ARM install in Parallels on my Drexel MacBook, and we still have Windows
on a desktop at home. That machine will get a refresh with either Debian
or Fedora in 2025, since it is too old to run Windows 11.</p>
<p>I’ve started using <a href="https://lima-vm.io/">Lima</a> for
managing Linux VMs on Macs (it provides a WSL2-like experience), and <a
href="https://github.com/abiosoft/colima">Colima</a> for hosting Docker
containers. I’m very happy with both; in particular, Colima’s seamless
support for x86_64 Docker containers via Rosetta 2 is very nice.</p>
</section>
<section id="data-and-code-management" class="level2">
<h2>Data and Code Management</h2>
<p>I’ve been moving away from git-annex for data management; it’s nice,
but it’s also more manual than I would like for a lot of cases, and
while it works ok on macOS, it’s very Linux-oriented.</p>
<p>For synchronizing large data archives (e.g., my academic portfolio or
teaching archive), I’ve gone back to <a
href="https://syncthing.net/">Syncthing</a>; once set up, it (mostly)
just works, and doesn’t require any manual steps for keeping data in
sync.</p>
<p>For assets in my website, I’ve switched back to <a
href="https://git-lfs.com/">git-lfs</a>. I set up Charm’s <a
href="https://github.com/charmbracelet/soft-serve">soft-serve</a> to
store git + git-lfs data without GitHub’s storage and bandwidth quotas,
and after a bit of juggling to figure out how to build it into a Podman
container with Tailscale support, it’s been excellent.</p>
<p>I’m mostly using WebDAV for other kinds of data servers these days,
especially for DVC remotes and my Kopia backups. <a
href="https://caddyserver.com/">Caddy</a> has been great for a
lightweight, efficient server (as well as a local development server for
web work), and I’m using Apache for more traditional setups (my web site
is published with Apache, as is my research group’s data repository).
They’re both mostly fine, each has capabilities the other lacks. I did
have problems when trying to use Apache <code>mod_dav</code> as a Kopia
remote, though — Kopia makes so many small transfers Apache was spending
all its time authenticating them, whereas Caddy handles the load without
problems.</p>
<p>I’ve switched from my own scripts on top of a Git repo to <a
href="https://chezmoi.io">chezmoi</a> for managing home directory config
files.</p>
</section>
<section id="development-and-analytics" class="level2">
<h2>Development and Analytics</h2>
<p>My development stack is mostly stable these days; mostly Python +
Rust + TypeScript (usually in <a href="https://deno.com">Deno</a>). I’m
not using Tcl as much as I was (and no longer use it in my website).
Interesting additions include:</p>
<ul>
<li><a href="https://just.systems">just</a> for task running in various
projects.</li>
<li><a href="https://pixi.sh">Pixi</a> for installing Conda-based
software environments; it has a better project and lockfile UX than
Conda and <code>conda-lock</code>.</li>
<li>Using PyTorch much more, including as the core of most LensKit
models.</li>
<li><a href="https://duckdb.org/">DuckDB</a> for large single-node data
processing; so far, it’s working better than other things I’ve tried,
including Polars and DataFusion, and has a much more stable developer
experience.</li>
<li><a href="https://docs.astral.sh/uv/">uv</a> for non-Conda Python
project management.</li>
</ul>
</section>
<section id="tools-and-utilities" class="level2">
<h2>Tools and Utilities</h2>
<p>I’ve picked up or changed a few of the smaller bits in my toolset as
well:</p>
<ul>
<li>Switched from Barkeeper to <a href="https://icemenubar.app/">Ice</a>
after Bartender was <a
href="https://9to5mac.com/2024/06/06/bartenders-developer-confirms-apps-acquisition/">sold
without transparency</a>.</li>
<li><a href="https://maccy.app/">Maccy</a> for clipboard history; works
as well as or better than Paste for my use cases and is cheap.</li>
<li>Adopted <a
href="https://www.parallels.com/products/toolbox/">Parallels Toolbox</a>
for a number of tools, including on systems without Parallels.
Particular tools I use from it include the system monitor, the disk
cleaner, and the uninstaller. I don’t like the Parallels menu bar hider
or clipboard history, they are significantly buggier than Ice or
Maccy.</li>
<li>Replaced iStatMenus with the Parallels system monitor, for now; I
was having Bluetooth monitoring performance problems with iStatMenus.
May revisit this.</li>
<li><a href="https://atuin.sh/">Atuin</a> for command-line history in
<code>zsh</code>.</li>
<li><a href="https://spaceship-prompt.sh/">Spaceship</a> for my ZSH
prompt.</li>
</ul>
</section>
<section id="future" class="level2">
<h2>Future</h2>
<p>I’m sure my stack will evolve in the next year, but I don’t have any
specific changes I’m looking to make.</p>
</section>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Catch me at RecSys 2024]]></title>
        <id>https://md.ekstrandom.net/blog/2024/10/recsys</id>
        <link href="https://md.ekstrandom.net/blog/2024/10/recsys"/>
        <updated>2024-10-08T04:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="right compact autosize">
<a href="/images/unsplash-bari-italy.jpg"><img srcset="/images/unsplash-bari-italy-320.jpg 1x, /images/unsplash-bari-italy-480.jpg 1.5x, /images/unsplash-bari-italy-640.jpg 2x" src="/images/unsplash-bari-italy-640.jpg" alt="Costal view of Polignano a Mare, Metropolitan City of Bari, Italy"></a>
<caption class="credit">
Photo by
<a href="https://unsplash.com/@zalfaimani?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Zalfa
Imani</a> on
<a href="https://unsplash.com/photos/a-large-body-of-water-next-to-a-rocky-cliff-v8C8SoBc2w4?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a>
</caption>
</figure>
<p>Inspired by Alan Said &amp; Sole Pera’s posts on LinkedIn, here’s a
quick summary of where you can find me and/or my work at RecSys next
week in Bar, Italy:</p>
<hr class="fold">
<ul>
<li><p>On Monday, I will be at the <strong><a
href="https://facctrec.github.io/facctrec2024/">7th FAccTRec
Workshop</a></strong> I am co-organizing with Toshihiro Kamishima,
Karlijn Dinnessen, and Amifa Raj.</p></li>
<li><p>In the Tuesday poster session, Christine Bauer, Andrés Ferraro,
and I have a short paper <a href="/pubs/not-you-me">It’s Not You, It’s
Me</a> on the relative impact of user response models and ranking
interventions on provider-side fairness over time.</p></li>
<li><p>Friday morning (first session), I am giving a <a
href="/talks/2024/roegen">keynote</a> at the <a
href="https://roegen-recsys2024.github.io/">ROEGEN workshop</a> (Risks,
Opportunities, and Evaluation of Generative Models in Recommender
Systems) on responsible recommendation, how generative models alter the
landscape of responsibility concerns, and how grounding our efforts in
the core goals and normative principles of recommendation can help us
navigate the shifting environment.</p></li>
<li><p>Friday morning (second session), I am giving a tutorial (with Joe
Konstan and Robin Burke) on how to run user experiments with the <a
href="https://poprox.ai">POPROX platform</a>.</p></li>
<li><p>Friday afternoon, I am co-organizing (with Sole Pera &amp; Alan
Said) the first <a href="https://altrecsys.github.io/">AltRecSys
workshop</a> on “Alternative, Unexpected, and Critical Ideas in
Recommendation”.</p></li>
<li><p>Throughout the conference, talking and getting meals and drinks
with many of you 😊.</p></li>
</ul>
<p>See you there!</p>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Hamlet and IR Social Impact]]></title>
        <id>https://md.ekstrandom.net/blog/2024/03/ecir-impacts</id>
        <link href="https://md.ekstrandom.net/blog/2024/03/ecir-impacts"/>
        <updated>2024-03-27T04:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="autosize right compact">
<a href="/images/unsplash-impact-water.jpg"><img srcset="/images/unsplash-impact-water-320.jpg 1x, /images/unsplash-impact-water-480.jpg 1.5x, /images/unsplash-impact-water-640.jpg 2x" src="/images/unsplash-impact-water-640.jpg" alt="Ripples on top of water with a rainbow pattern reflected."></a>
<figcaption class="credit">
Photo by
<a href="https://unsplash.com/@jordanmcdonald?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Jordan
McDonald</a> on
<a href="https://unsplash.com/photos/time-lapse-photography-of-water-drop-vkx0kgKx9VA?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a>
</figcaption>
</figure>
<blockquote>
<p>There are more intervention strategies in heaven and earth, Horatio,
than are dreamt of in your philosophy.<br />
— Hamlet, sort-of</p>
</blockquote>
<p>Tomorrow, I will be presenting our paper in the <a
href="https://www.ecir2024.org/ir4good/">IR4Good track</a> at ECIR 2024
on <a href="/pubs/ecir-impacts">strategic IR impact
interventions</a>.</p>
<p>From my side, this paper grew out of a couple of things:</p>
<ul>
<li>Resonance between what I am trying to accomplish by working on
information access fairness and what <a
href="https://solepera.github.io">Sole</a> is doing with IR for
understudied and underserved users (also discussed in my <a
href="/talks/2024/ir4u2">IR4U2 keynote</a>).</li>
<li>Frustration with what I perceive as an overemphasis on algorithmic
fixes and metric disparities, when there are a wide range of metrics,
intervention sites, and strategies that we can use to improve social
impacts such as fairness, harm reduction, etc. in information access
systems.</li>
</ul>
<p>Lex &amp; Henriette joined us to broaden the argument to deal with an
expansive notion of “impacts” of IR systems, including discrimination,
misrepresentation, utility, accessibility, etc., and argue that <span
class="hl-blue"><strong>effective</strong></span> IR impact work has
four key characteristics:</p>
<ol type="1">
<li>It is <span class="hl-purple"><strong>grounded</strong></span> in
specific impact goals.</li>
<li>It <span class="hl-cyan"><strong>contextualizes</strong></span>
those impacts and goals in the broad landscape of the ways an IR system
can affect its consumers.</li>
<li>It flows from <span class="hl-magenta"><strong>creative and
expansive thinking</strong></span> about the range of possible methods
and intervention sites for addressing that impact.</li>
<li>It <span class="hl-red"><strong>matches</strong></span> the impact
to a strategy that is appropriate given technical and organizational
constraints and the specific ethical, legal, economic, and other
dimensions of the impact concern.</li>
</ol>
<p>We also provide three “maps” of impact goals, candidate
operationalizations, and intervention sites &amp; strategies to support
such work. There is a wide range of possible intervention sites and
corresponding strategies for adjusting IR system impact, some of which
are not widely-discussed. One in particular is the <strong>engineering
process</strong>: if we have a robust mediation analysis that identifies
the <em>causes</em> of an impact or impact disparity, that can be used
to inform process planning. For example, if users of a particular group
are receiving lower-quality service, and it seems to be because the
system is less effective at handling certain types of queries that they
use more often, then the team could prioritize efforts on improving the
system’s handling of those queries over other improvements that will
primarily benefit users who already receive good results.</p>
<p><a href="/pubs/ecir-impacts">Read the paper</a> for more details, and
check out the talk if you’re at ECIR!</p>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Leveling Up Call and Video Quality]]></title>
        <id>https://md.ekstrandom.net/blog/2024/01/zoom-lecture-studio</id>
        <link href="https://md.ekstrandom.net/blog/2024/01/zoom-lecture-studio"/>
        <updated>2024-01-03T05:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="right compact autosize">
<a href="/images/drexel-office-endpoint.jpg"><img srcset="/images/drexel-office-endpoint-320.jpg 1x, /images/drexel-office-endpoint-480.jpg 1.5x, /images/drexel-office-endpoint-640.jpg 2x" src="/images/drexel-office-endpoint-640.jpg" alt="My office setup, with a MacBook Pro next to a large monitor with a Shure MV7x mic on a boom stand."></a>
</figure>
<p>I often get comments, and sometimes questions, about my Zoom and
video lecture setup in my office. In this post, I want to talk a bit
about it, my choice of equipment, and things that can improve the
quality of video recordings, remote teaching, and videoconferences.</p>
<p>There are a few key pieces to this:</p>
<ol type="1">
<li><a href="#microphone">Audio quality (microphone)</a></li>
<li><a href="#camera">Video quality (camera)</a></li>
<li><a href="#lighting">Lighting</a></li>
<li><a href="#software">Software</a></li>
<li><a href="#monitoring">Headphones and Monitors</a></li>
<li><a href="#more">Additional Useful Bits</a></li>
</ol>
<p>Additional, much more expensive investments — like soundproofing room
treatments — can really up the game, but I’m going to leave those out of
scope. There is a lot you can do without remodeling your house.</p>
<p>With few exceptions, I’ve actually used the products I talk about
here, most of them fairly extensively. For a quick TL;DR, here are a few
good choices:</p>
<ul>
<li>For a reasonably-priced, relatively portable setup, get the <a
href="https://www.jabra.com/business/office-headsets/jabra-evolve/jabra-evolve-40">Jabra
Evolve 40</a> headset and use your laptop or phone camera. The Jabra mic
isn’t as good as the Yeti or Shure, but it provides clear audio without
Bluetooth artifacts or picking up the rest of the room.</li>
<li>For a good and moderately-priced upgrade for a stationary office
setup, I recommend the <a
href="https://www.logitechg.com/en-us/products/streaming-gear/yeticaster-pro-streaming-microphone-bundle.988-000107.html">Yeticaster</a>,
the <a href="https://us.ankerwork.com/products/a3369">Anker PowerConf
C200</a> webcam, and a comfortable set of headphones. The <a
href="https://www.shure.com/en-US/products/microphones/mv5?variant=MV5-B-DIG">Shure
MV5</a> is also quite good, and is more compact and less expensive than
the Yeti.</li>
</ul>
<section id="microphone" class="level2">
<h2>Microphones</h2>
<p>Audio quality is, in my opinion, the biggest boost to video recording
or conference quality — visuals are good and important, but sound
quality is vital to making sure you are clearly understood. A good
quality microphone setup will do a few useful things:</p>
<ul>
<li>Clearly capture your voice.</li>
<li>Not capture other sounds in the background.</li>
</ul>
<p>There are other things you probably want to consider as well, such as
form factor, size and weight, and price; different ones will also fit
different spaces better. I currently use 3 different microphones for my
home office, university office, and on the go. Microphones are also a
place where <a
href="https://www.nytimes.com/wirecutter/reviews/the-best-usb-microphone/">Wirecutter’s
recommendations</a> are pretty good.</p>
<aside class="callout note">
Wirecutter refreshed their recommendations in Feb. 2024, after I wrote
this post. The Yeti is no longer their top pick, and they give some good
reasons why (basically, the competition advanced and Yeti didn’t keep
up). I don’t have any experience with their current round of picks, but
they’re definitely worth looking at. In particular, I’d look hard at the
Rode PodMic instead of the Shure MV7X if I were shopping again today.
</aside>
<p>I also recommend a microphone setup that connects to your computer
with USB rather than the microphone jack: this keeps the analog audio
processing circuitry separate from your computer to reduce EM noise
interference with your audio signal. MacBooks have pretty clean audio
processing, but PCs can be a mess in that regard.</p>
<p>For capturing just your voice, either for a recording or a conference
call, you probably want a <a
href="https://en.wikipedia.org/wiki/Cardioid"><em>cardioid</em></a>
microphone: the microphone’s pickup pattern focuses in front of the mic,
with little response to sound from the sides or rear; this improves
sound isolation (while requiring you to be somewhat careful how you
position the mic). For other contexts, you need other pickup patterns:
for a group of people, for instance, you need an omnidirectional
microphone.</p>
<div class="fig-panel">
<figure class="autosize">
<a href="/images/studio/mv7.jpg"><img srcset="/images/studio/mv7-600.jpg 1x, /images/studio/mv7-900.jpg 1.5x, /images/studio/mv7-1200.jpg 2x" src="/images/studio/mv7-1200.jpg" alt="A Shure MV7 microphone."></a>
<figcaption>
A Shure MV7 microphone on a boom arm. Photo by
<a href="https://unsplash.com/@bradlembach?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Bradley
Lembach</a> on
<a href="https://unsplash.com/photos/black-and-silver-microphone-with-stand-iNJiI6C9FL8?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a>.
</figcaption>
</figure>
<figure class="autosize">
<a href="/images/studio/yeti.jpg"><img srcset="/images/studio/yeti-600.jpg 1x, /images/studio/yeti-900.jpg 1.5x, /images/studio/yeti-1200.jpg 2x" src="/images/studio/yeti-1200.jpg" alt="A black Yeti microphone on a boom arm with shock mount."></a>
<figcaption>
Blue Yeti microphone on boom arm. Photo by
<a href="https://unsplash.com/@rubsols?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Ruben
Boekeloo</a> on
<a href="https://unsplash.com/photos/a-microphone-hanging-from-the-ceiling-jRuWBndqhvQ?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a>
</figcaption>
</figure>
</div>
<p>If you watch video podcasts, you probably see 2–3 microphones a lot:
the Blue Yeti, the Shure MV7, and the MV7’s older and larger brother,
the Shure SM7. Both the Yeti and the MV7 are excellent microphones. I
haven’t used the SM7, but assume it is quite good; it is most often used
for vocal recording in studio environments, while it is my understanding
that the MV7 is designed for use in less sonically refined spaces like
offices with poor sound isolation, and from my experience it is very
good in them.</p>
<section id="blue-yeti" class="level3">
<h3>Blue Yeti</h3>
<p>I used the Yeti for several years at Boise State, including to record
my <a href="https://cs533.ekstrandom.net/week1/">data science
lectures</a>. Mine was in the <a
href="https://www.logitechg.com/en-us/products/streaming-gear/yeticaster-pro-streaming-microphone-bundle.988-000107.html">Yeticaster</a>
configuration, with shock mount and boom arm; a boom arm helps you
position the mic closer to your face to make it easier to pick up your
vocals without background noise, and the shock mount reduces the amount
of keyboard clicks, desk jostles, etc. that make it into the sound. I
found both the sound quality and the sound isolation of the Yeti to be
excellent, and it did a very good job of capturing my voice without also
recording our furnace’s attempt to mimic a malfunctioning jet-powered
freight train. The Yeti is also a very flexible microphone, with 4
different pickup patterns: the cardioid is good for recording and
conferencing, while the bidirectional and omnidirectional patterns are
good when you want to record both yourself and a guest with one mic.</p>
<p>I generally recommend the Yeti for people looking to upgrade a
stationary office setup. The combination of sound quality and
flexibility is good for most people (starting to sound like Wirecutter
here, I know…). It’s also relatively reasonably priced (often $100–130
for the mic itself; the Yeticaster setup lists for $199 but I’ve seen it
on sale on Amazon for $130–150). It is important to note that when the
Yeti is in cardioid mode, the front of the mic is the front
<em>side</em>, not the tip. The Blue logo should face you, and you talk
into the side of the microphone.</p>
<p>Whether you should get a desktop stand or a boom stand depends on
your preferences and the constraints of your desk space. I like the boom
stand, both to get the microphone closer to my mouth and to not take up
as much desk space; the boom also swings out of the way when not in use.
It does occlude vision a little, but I got used to that and have no
problem seeing my monitor with the mic in my peripheral vision. Note
that the Yeticaster configuration consists of the Yeti microphone, the
Compass boom arm, and the shock mount, but does <em>not</em> include the
desktop stand that you get if you just buy the Yeti on its own. The Yeti
also has a standard 5/8” threaded base for mounting on other stands.</p>
</section>
<section id="shure-mv7" class="level3">
<h3>Shure MV7</h3>
<p>As I noted in my <a href="/blog/2023/12/tools">tools report</a>, when
setting up at Drexel I decided to change things up and get the <a
href="https://www.shure.com/en-US/products/microphones/mv7?variant=MV7-K">Shure
MV7</a>. There were a few reasons for this:</p>
<ul>
<li>I like Shure microphones in general (there is a reason they are the
standard in a wide range of situations).</li>
<li>The MV7 is lighter and less bulky (and takes less space in my field
of vision on the boom arm).</li>
<li>The online reviews I read indicated the MV7 is exceptional at sound
isolation in untreated rooms like my office (the Yeti was already good
in this regard, but I partly wanted to see what the Shure could
do).</li>
</ul>
<p>I haven’t regretted this decision at all — the MV7 is an excellent
microphone. It is, however, more expensive (USB version lists for $249),
and the USB controls are also harder to access (on the top of the mic,
tilted away from you, instead of on the front).</p>
<p>I actually bought the MV7X model, which omits the USB interface in
favor of only providing an XLR jack, and connected it to a <a
href="https://us.focusrite.com/products/vocaster-one">Focusrite Vocaster
One</a> dedicated USB interface. With this setup, the control ergonomics
on the microphone aren’t an issue because I control volume and hardware
mute from the Vocaster sitting on my desk; this also allows me to
directly connect my headphones to the interface without trying to run
their cable to the back of the microphone, and gives me an easy hardware
volume control for the headphones. The Vocaster is a great piece of kit;
in particular, its auto-gain feature means I don’t have to worry about
levels basically at all. I only have two complaints about the
device:</p>
<ul>
<li>I can’t control or disable sidetone (the monitoring signal feeding
from the microphone into your headphones to help you regulate your
voice); since I use open-back headphones, I don’t really need sidetone
and would just as soon turn it off, but no dice.</li>
<li>The hardware mute does not synchronize with Zoom’s software mute, so
I have to make sure both mutes are off in order to talk. Some hardware
mute buttons can synchronize, but the drivers aren’t set up to do that
with the Focusrite at this time.</li>
</ul>
<p>I have an old Mackie Onyx Blackjack interface at home that I’m not
really using, I might take that into the office and see how much I miss
the auto-gain. We’ll see. I’m generally very happy with the MV7X +
Vocaster setup, though; sounds great, doesn’t pick up much background
noise, and auto-gain is my friend. This is a more advanced and expensive
setup, though, so I would generally recommend the Yeti.</p>
<p>I mounted the MV7X on a scissor-style boom arm I found on Amazon. It
uses a standard 1/4” threaded mount (the other standard size for
mounting A/V gear).</p>
<aside class="callout note">
I haven’t tried one myself, but Wirecutter is now recommending the <a
href="https://rode.com/en/microphones/broadcast/podmic">Rode PodMic</a>;
as an upgrade pick they specifically recommend its USB variant, but it’s
also available in XLR. It’s notably less expensive than the MV7 and is
likely a worthy contender for the XLR + Vocaster setup.
</aside>
</section>
<section id="shure-mv5" class="level3">
<h3>Shure MV5</h3>
<p>At home, my desk area is not as spacious, and I don’t really have
room for a boom arm (or a large desktop microphone), so based on
Wirecutter’s recommendation I bought the <a
href="https://www.shure.com/en-US/products/microphones/mv5?variant=MV5-B-DIG">Shure
MV5</a> at home. It stays out of the way, works pretty well, and the
sound quality is pretty good even with it just sitting on the desk
instead of being positioned more optimally near my mouth. It’s a great
little piece of gear. Its hardware controls do have some ergonomic
difficulties (both mute button and mute indicator are on the back, so I
wouldn’t want to use them as my primary mute controls while on a call;
the headphone level control is also a tiny knob that’s hard to reach,
but I don’t use its headphone output anyway).</p>
<p>The MV5 is also less expensive than the Yeti, so if either space or
funds are at a premium, I second Wirecutter’s recommendation that it’s a
great little mic.</p>
</section>
<section id="sound-on-the-go" class="level3">
<h3>Sound on the Go</h3>
<p>All of these microphones are for stationary office setups (well, you
could take the MV5 with you, but I probably wouldn’t). On the go, you
can still get some nice improvements.</p>
<p>When we all went home for COVID and I needed something better than my
Surface’s built-in mic fast, I bought a <a
href="https://www.jabra.com/business/office-headsets/jabra-evolve/jabra-evolve-40">Jabra
Evolve 40</a>, and continued to use it as my at-home calling headset
after partially returning to the office. The sound quality (either
recording or playback) is definitely not as good as the other equipment
I recommend, but it’s pretty good, and for $100 you get both mic and
headphones in one package that can fit in your laptop bag. It’s a pretty
good deal for a first upgrade or for a secondary on-the-go setup.</p>
<p>I like to travel <em>very</em> light, however, so long-term I wanted
something more compact to toss in my bag for on-the-go calls (and
occasionally teaching from a hotel room). I use the now-discontinued <a
href="https://www.jabra.com/supportpages/jabra-elite-85t#/#100-99190000-02">Jabra
Elite 85t</a> for travel and as my primary earphones for my phone; the
call quality is very good for true wireless earbuds, and they sound
great listening to music as well. Unlike most wireless earbuds, they do
not provide a complete seal, allowing some ambient noise through; I find
it difficult to talk with my ears sealed, so this is very helpful for
me. They also have active noise cancellation, a hear-through mode, and
configurable sidetone, so I can dial them in to the sonic landscape I
want for a particular situation. Very happy with them, and recommend
them if you can find a pair; in general, though, Jabra makes pretty good
on-the-go audio setups, so a newer Jabra set that matches your needs is
likely to be pretty good (and the Elite 7 Pro’s bone conduction
microphone capabilities look very interesting).</p>
</section>
</section>
<section id="camera" class="level2">
<h2>Cameras</h2>
<p>MacBooks, iPhones, and iPads, as well as higher-end Android phones,
all have very good cameras. However, if you are using an external
monitor, or have a PC laptop, you probably want a webcam. A good camera
makes it easier for others on the call or watching the video to see you,
and also provides a better image for background removal (either with
AI-powered virtual backgrounds or traditional chroma-key).</p>
<aside>
There might be some PC laptops with good webcams, but I’m not sure what
models they would be. My last PC was a Surface Laptop 4, which one would
expect to be decent given the price and target market; its camera was
remarkably bad, especially in medium- to low-light conditions.
</aside>
<p>For an effective camera, I like to see a few things:</p>
<ul>
<li>Full HD (1080p) resolution — even though final video is most often
in smaller windows on Zoom or shrunk down in a lecture video, having
full HD gives me a high-resolution original for video editing, and lets
me use the camera video at full frame for intros/outros and other
contexts where I’m not combining with other visual elements (I render
all my lectures and other recorded videos at full HD resolution).</li>
<li>Clear, crisp image without significant noise or grain in normal
office lighting. Supplementary lighting is useful for a quality boost in
recordings that need to meet a higher quality standard, but I don’t want
to need to shine extra lights at my face for all of my calls and utility
recordings.</li>
<li>Reasonably accurate and vibrant color. This will vary a lot; Apple
does significant image processing to ensure that their products’ camera
images look clear and vibrant. Other cameras often have fuzzier or less
saturated colors, and a cheap off-brand webcam we bought for home use
when name brands were hard to get in 2020 has truly terrible color
reproduction.</li>
</ul>
<p>I’ve used a few different external webcams in recent years:</p>
<ul>
<li><a
href="https://www.logitech.com/en-us/products/webcams/brio-4k-hdr-webcam.960-001105.html">Logitech
Brio 4K Pro</a> (MSRP $200)</li>
<li><a
href="https://www.logitech.com/en-us/products/webcams/c920s-pro-hd-webcam.960-001257.html">Logitech
C920s</a> (MSRP $70)</li>
<li><a href="https://us.ankerwork.com/products/a3369">Anker PowerConf
C200</a> (MSRP $60)</li>
<li><a
href="https://www.logitech.com/en-us/products/webcams/c615-webcam.960-000733.html">Logitech
C615</a> (MSRP $50)</li>
</ul>
<p>I used a Brio as my primary webcam at Boise State from late 2021
onward. To save some startup funds at Drexel, I went with the C920s for
my university office (Wirecutter’s <a
href="https://www.nytimes.com/wirecutter/reviews/the-best-webcams/">former
pick</a>), and I recently bought the Anker C200, Wirecutter’s budget
pick, for my home office. The C615 (or its predecessor) was the first
webcam I bought at Boise State back in 2016.</p>
<p>Of these, the Brio has the best image quality by far, but at a steep
price. I’m not very happy with the C920s; the image is pretty grainy,
and it isn’t enough better than the less expensive cameras to really
feel worth it. The Logi Capture app that is necessary to change camera
settings, like the focal length, also doesn’t support modern Macs
(there’s a new “Logi Tune” that I haven’t tried yet). For its price
point, I’m very happy with the Anker, and the built-in shutter is pretty
neat (and easier to use than the flip-cover on the C920s).</p>
<p>My recommendation, therefore, is to get the <strong>Anker
C200</strong> for a stationary office setup. I don’t have any experience
with the newer, less expensive (MSRP $130) Brio cameras that Wirecutter
recommends; they’re probably good, but it’s a question of whether they
are worth the premium over the Anker. From time to time, I’ve considered
the Razer Kiyo to get the built-in light, but haven’t found it necessary
yet (and external lighting is pretty easy to set up).</p>
<p>On the go, just use your laptop camera, at least if it’s a MacBook.
If you have a low-quality PC camera, there are portable webcams you can
get to clip on a laptop display, but I have no idea how good they are.
It would probably work just as well to put your phone on a tripod and
use that, either by joining the call from the phone or using an app to
use it as a webcam for your laptop (more on that in the next
section).</p>
<section id="secondary-camera" class="level3">
<h3>Secondary Camera</h3>
<p>That’s all for the primary webcam for capturing yourself for calls
and lectures in a stationary office setting. A secondary camera can be
quite useful, though:</p>
<ul>
<li>Document camera for hand-drawn diagrams, notes, etc., if that’s your
style. I like analog media, so if I’m going to draw freehand to support
a lecture or to show something in a meeting, I’d usually rather do it
with a pen and paper under a document camera than with a tablet.</li>
<li>Room or speaker capture for lectures, talks, panels, etc. Sometimes
I do this for class, but if you’re organizing workshops or things like
that it’s useful to be able to do DIY livestreaming, depending on the
facilities the host venue makes available.</li>
</ul>
<p>For these purposes, if you have a decent smartphone, it’s probably
the best bet. Smartphone cameras are very good these days, especially in
flagship iPhone models. You can use the phone directly to record video
or join a Zoom call; I’ve often done this for recordings or workshop
rooms. It’s also possible to use a phone as a webcam for your main
computer to record or share on a video conference. There are a few ways
to do this (my instructions here are for iPhone; similar thing are
likely possible on Android, but I don’t know the software):</p>
<ul>
<li>Recent versions of macOS and iOS directly support using your iPhone
as a webcam. This only works if you are signed in to both the computer
and the phone with the same Apple ID; if, like me, you use separate
Apple IDs for work and personal devices, this option isn’t
available.</li>
<li><a href="https://reincubate.com/camo/">Reincubate Camo</a> is a very
good application that lets you use your phone as a webcam (works for
both iPhone and Android), connecting over the USB or Lightning cable.
Its pricing is pretty reasonable, and they have educational discounts.
The desktop app provides a dashboard that lets you control which camera
it’s using along with other settings like zoom.</li>
<li><a href="https://obsproject.com/">Open Broadcaster Software</a>
provides a lot of camera and scene management capabilities, and can
export its output as a virtual webcam that other applications can pick
up. It’s really oriented towards livestreamers, but I find it useful
when I need more live video composition or extra capture for recording.
I most often use it as just a conduit for my phone camera: the <a
href="https://obs.camera/">Camera for OBS Studio</a> iPhone app lets you
connect your camera like Reincubate. The OBS desktop experience isn’t as
easy-to-use as Camo, but it works well enough. It’s my current setup for
phone-to-desktop, both for recording and to be a document camera in
class since at least some classrooms in my department don’t have
document cameras.</li>
</ul>
<p>For mounting, I use the <a
href="https://www.quadlockcase.com/">QuadLock</a> system — it’s secure
and durable, and seems less expensive overall than the competing system
from Peak. It’s great for putting my phone on my bike, but also for
video work. In my office, I have the <a
href="https://www.quadlockcase.com/collections/shop-camera/products/tripod-adaptor?variant=213101141">tripod
adapter</a> on a second scissor boom arm for document camera work (it
does need a <a href="https://www.amazon.com/gp/product/B00DA38C3G/">ball
mount</a> to really work well, but connects to any standard 1/4” tripod
screw). I also carry the <a
href="https://www.quadlockcase.com/collections/shop-camera/products/tripod-selfie-stick?variant=42576263970987">tripod
/ selfie stick</a> in my bag for portable recording or streaming.</p>
</section>
</section>
<section id="lighting" class="level2">
<h2>Lighting</h2>
<p>Lighting is crucial to high-quality photography and videography;
above a baseline camera quality threshold (which is sadly not met by a
lot of built-in or budget webcams), better lighting probably improves
quality more than better cameras. There are a lot of usable, inexpensive
lighting options on places like Amazon; LED light panels and ring lights
can help a lot with clear face capture, although the glare and lighting
intensity can be quite distracting if you aren’t used to it. Diffuse,
indirect lighting is good and helps reduce glare, but the equipment is
often more expensive.</p>
<p>Before Camtasia got support for AI-based background removal, I used
chroma-key for my lecture videos, and that depends a lot on good,
uniform lighting on the background. I bought a few cheap 5–7”
USB-powered LED lighting panels, and they got the job done, but weren’t
very good; if I were setting up that kind of lighting again, I would
probably invest in a more expensive panel. I haven’t personally found
ring lights to be very useful, but a lot of people use them.</p>
<p>I don’t have any dedicated lighting right now. If I were engaging in
a significant buildout like our MOOC or my data science video class
again, I would probably invest in lighting, but for Zoom calls and the
odd recorded presentation or lecture, I find my office lights to be
adequate.</p>
</section>
<section id="software" class="level2">
<h2>Software</h2>
<p>For videoconferencing, the software is pretty set: Zoom, Teams,
Google Meet, or whatever your meeting is using.</p>
<p>For video recording and editing, I use <a
href="https://www.techsmith.com/">Camtasia</a>. Its user experience is
heavily optimized for lecture and demonstration videos, so while it
lacks a lot of the flexibility in terms of effects and editing that a
more general package like Final Cut Pro or Adobe Premier would offer,
it’s much easier to use for academics and has very good support for
screen capture, including slides in videos, annotating the screen
(e.g. putting in callouts to highlight important screen elements), and
basic transitions and effects. I wouldn’t call it the most polished
piece of software, but it gets the job done.</p>
<p>I don’t do a huge amount of editing on most of my videos, but do
clean up significant false starts (and when I detect such a false start
while recording, I’ll usually pause for several seconds and restart at a
clear point like the beginning of the slide to give myself a clear point
to edit in the final production). I usually also do a bit with how the
slides and speaker videos are arranged (often replacing my speaker
background with transparency or another image), and sometimes put in
some intro/outro effects and a title card. TechSmith recently added
virtual background replacement to Camtasia (in 2020, the only way to do
it was with a green screen and chroma-key).</p>
<p>TechSmith Audiate is a very useful application that complements
Camtasia. Audiate uses speech-to-text to transcribe your video’s audio.
You can then edit the resulting text to clean up transcription errors
(so the transcript can be used for higher-quality captions), but the
really interesting thing is that you can <em>delete</em> text such as
disfluencies and false starts. When you then export the project back to
Camtasia, it includes an edit list to edit those bits out of the video
track as well. Polishing up video in this way is very time-consuming
when you try to do it directly in the video editor, but Audiate makes it
practical, and lets you do the caption cleanup and video edit in one
pass through your recording. If you want linguistically-clean recordings
and don’t have budget for dedicated captioning and video editing labor,
it’s a pretty good option. I don’t have precise measurements, but it
probably takes me about 1–1.5 minutes of editing time per minute of
video to polish a video with Audiate. The biggest downside to Audiate is
its price, and while many universities have site license to Camtasia
and/or SnagIt, Audiate site licenses are less common; I’m therefore not
currently using it, but would get it again for a significant recording
effort that demands the higher level of polish it affords.</p>
<p>I still want to provide good-quality captions for my videos, though,
so I’m using <a
href="https://goodsnooze.gumroad.com/l/macwhisper">MacWhisper</a> for
transcription and captioning. Its speech-to-text capabilities are quite
accurate (better than anything else I’ve used, including Audiate), and
its automatic segmenting logic is also very good (Audiate was not great
at that, and also didn’t provide very good manual segmenting controls
when last I used it). My usual captioning workflow is this:</p>
<ol type="1">
<li>Export the audio track from Camtasia (Export → Audio Only).</li>
<li>Import and transcribe the audio track with MacWhisper.</li>
<li>Export the transcription to SRT-format subtitles.</li>
<li>Import the subtitles back into Camtasia.</li>
</ol>
<p>Any further edits, or partial exports, also correctly affect the
subtitles after this. If there is some egregiously bad segmenting, I’ll
sometimes tweak up the subtitles using Camtasia’s subtitle editor; while
Camtasia’s editor is extremely tedious for drafting subtitles, it isn’t
bad for light editing of subtitles that are already there.</p>
<p>I edit and export all of my videos at full HD (1920×1080) resolution,
typically to a Local File that I then upload to Panopto, Kaltura,
YouTube, Bunny, or wherever I am publishing the video in question. With
the subtitles in place, Camtasia’s default for a local file export is to
“burn” the subtitles into the video (called <em>open captioning</em>); I
usually turn this off, re-export the subtitles to an SRT or VTT file,
and upload them separately as a caption file to the video hosting
service after uploading the video. This allows the viewer to control
whether captions are displayed or not (<em>closed captioning</em>).</p>
</section>
<section id="monitoring" class="level2">
<h2>Headphones and Monitors</h2>
<p>Ok, all of that is about creating and sharing the video. What viewing
and listening during production, and hearing and seeing the people
you’re calling with?</p>
<p>Most of this is covered by general thinking about what kind of
monitor and headphones or speakers you want. I recommend getting a
relatively good monitor so the color reproduction is decent; this makes
it easier to catch significant color or lighting problems in your video.
I use the 27” 4K ASUS ProArt monitor; this is a professional-grade
color-calibrated monitor. Full color calibration isn’t necessary for
basic educational videos (and I don’t do color-calibrated work), but you
do want pretty good color calibration. Dell UltraSharp monitors are
pretty good, I’ve used those in the past; you mostly want to stay away
from the entry-level monitors (especially things costing $200 or less).
I have never used an Apple Studio Monitor, but don’t think they’re
likely worth the very expensive premium they command — a pro-level ASUS
or Dell monitor is fine, and costs $300–500 instead of $1500 or
whatever.</p>
<p>For audio, I strongly recommend a good pair of headphones. While some
calling software has pretty good software echo cancellation these days,
using headphones while on call means there isn’t echo that the software
needs to try to cancel. If you like listening to music while working, a
good set of headphones will also usually be higher-quality sound than
laptop or cheap desktop speakers.</p>
<p>Selecting headphones is a pretty personalized decision. A few things
to think about in identifying the ones that will work for you:</p>
<ul>
<li><strong>How do you want them to fit on your ears?</strong> Besides
earbuds, the two major designs are <em>circumaural</em> where the
headphone pad goes around your ears, and <em>supra-aural</em> where the
pad goes on your ears. There are also very different supra-aural
designs; small, often circular headphones like the <a
href="https://www.jabra.com/business/office-headsets/jabra-evolve/jabra-evolve-40">Evolve</a>
rest gently on your ears, mostly towards the center, while larger and
heavier ones press more firmly on the outer parts of your earlobes. Your
particular ear size and geometry will also make a significant difference
on how they fit.</li>
<li><strong>Do you want sound isolation?</strong> Curcumaural and large
supra-aural <em>closed-back</em> headphones block out a good deal of
background noise to provide good isolation on the sound that you hear.
They also block out a lot of your own voice, so it can sound weird to
talk while wearing them. It’s also harder to calibrate your vocal volume
since you can’t hear yourself very well (sidetone helps with this).
<em>Open-back</em> headphones do not isolate: the back of the headphone
(away from your ear) is open, usually a plastic or metal mesh, so
ambient sound can come in. Earbuds are also available in both
sound-isolating and non-isolating varieties, depending on whether they
are designed to seal your ear, in addition to having or lacking active
noise cancellation.</li>
</ul>
<p>What you connect the headphones to also makes a significant
difference. I’ve mentioned “sidetone” a few times; this refers to signal
from the microphone going into your earpiece (in pro-audio terms, it’s
usually called “monitoring”; sidetone is what it’s usually called in
telephony). It’s helpful for regulating your vocal volume, since our
brains rely on auditory feedback for that process. Sidetone or
monitoring is especially important with sound-isolating or
noise-cancelling headphones or earphones, because they block or cancel
your natural voice feedback along with the background noise. Any of the
USB microphones I mentioned above actually have a headphones jack on
them as well, so you can plug in your headphones to get <em>direct
monitoring</em>: microphone signal directly goes to the headphones
without going through the digital processing or your computer, so you
get zero-latency monitoring of your voice. USB audio interfaces for
podcasting or recording, like the Vocaster I use, also provide this
feature. You can configure your computer to do monitoring with the
headphones plugged in to your headphones jack, but this introduces a
small amount of latency (the computer digitally encodes the audio
signal, processes it through its audio stack, and decodes it back into
an audio signal for the headphones); we’re talking about milliseconds at
most, but it’s noticeable.</p>
<p>I personally find it very difficult to talk naturally with
sound-isolating headphones and have had mixed success relying entirely
on sidetone, so I prefer to use circumaural open-back headphones at my
desk and non-sealing earbuds on the go (I do use the earphones’
sidetone, and I’ve started to get used to it, but I don’t usually aim
for my highest-quality recording in that setting). The specific gear I
run:</p>
<ul>
<li><p><a
href="https://www.sennheiser-hearing.com/en-US/p/hd-600/">Sennheiser
HD600</a> open-back circumaural headphones in my university office (MSRP
$450, I got them on sale for $250–300). Sennheiser is one of the
best-known names in headphones (like Shure is the go-to for
microphones), for good reason. I like using reference headphones (flat
frequency response designed for professional monitoring and production),
and the HD600 is in that category. This isn’t a particularly common
taste, and is influenced by some time I spent doing audio production in
my younger days; many people will probably find some of Sennheiser’s
other models, like the Momentum or the HD560, to have a more familiar
tonal quality.</p>
<p>I’m extremely happy with the HD600s — they are comfortable and secure
while also feeling light on my head, and sound fantastic. They also fit
comfortably around my ears.</p></li>
<li><p><a
href="https://www.shure.com/en-US/products/headphones/srh1440?variant=SRH1440">Shure
SRH1440</a> open-back circumaural headphones for my home office. These
were the first open-back headphones I bought. I like their sound a lot
(and they definitely sound better than anything I had purchased before),
but the find the HD600 to both sound better and feel more comfortable
(the SRH1440 is noticeably heavier and the pads are relatively thick, so
it feels a bit like my head is in part of a football helmet).</p></li>
<li><p><a
href="https://www.jabra.com/supportpages/jabra-elite-85t#/#100-99190000-02">Jabra
Elite 85t</a> earbuds for travel &amp; on-the-go calling. I talked about
these more under <span id="microphones">microphones</span>, but they’re
a great (although not cheap) set of day-to-day true-wireless earphones.
They are unsealed, which is not common for this level of earphone, so
I’m a lot more comfortable calling on them than on sealed earphones.
They also have both sidetone and hear-through, making them quite
versatile for a range of listening and calling situations. Most of the
Jabra models (at least at the higher-end) have sidetone, so if you want
sealed earphones, they have several options.</p></li>
</ul>
<p>I no longer have the <a
href="https://www.jabra.com/business/office-headsets/jabra-evolve/jabra-evolve-40">Jabra
Evolve 40</a> (they were owned by the university), but as noted a couple
times above, they’re a pretty good and semi-portable headset. I wouldn’t
want to spend a lot of time listening to music on them, and they aren’t
outstanding for listening while doing video editing and post-production,
but they’re fine and worth the money.</p>
<p>One last headphone to note is the <a
href="https://pro.sony/ue_US/products/headphones/mdr-7506">Sony
MDR-7506</a>. These are a professional-grade closed-back supra-aural
headphone that Sony has had in production for over 30 years now. For my
ears, they aren’t the most comfortable for extended periods of
listening, but they sound great and are built to last. They also fold up
into a relatively bulky bag for some portability (could toss them in a
suitcase, but they’d take up quite a bit of a backpack). They’re a
workhorse in the professional live sound world, and earn that
reputation. If I started to do live sound work again for some reason, I
would definitely buy a pair. In the office, I prefer the Sennheiser.</p>
<p>I don’t have particular recommendations for other types or formats of
headphones. Sennheiser generally makes good headphones, so it’s hard to
go wrong looking through their product lineup for something that meets
your needs; Jabra is generally good as well, but is definitely more
optimized for office use than music or video production.</p>
</section>
<section id="more" class="level2">
<h2>Additional Bits</h2>
<p>That covers most of it. There are a few other things that can be
useful:</p>
<ul>
<li><p>For extensive video editing, a <a
href="https://contourdesign.com/products/shuttle-xpress">shuttle
controller</a> is extremely useful. This lets you easily “scrub”
backwards and forwards in the video to find the precise points to edit,
and most of them provide additional buttons that you can map to keyboard
shortcuts to perform common operations like splitting a video element or
deleting selected video. Microsoft’s Surface Dial is also supposed to
function as a shuttle, but I don’t have any experience with it.</p></li>
<li><p>Quick access to a mute button is helpful on calls. Zoom provides
some kind of global keyboard shortcut to mute/unmute on most platforms.
My keyboard is configurable, so I have set the volume knob to send that
key signal when I press it in. In the office, I also have the hardware
mute button on the Vocaster.</p></li>
<li><p>Video hosting can be a little trick. Both of my recent
universities have contracted with a hosting platform (Kaltura at Drexel,
Panopto at Boise State); that’s the best option for class videos, since
it integrates with the LMS and also complies with the university’s data
storage and access policies. For other videos such as talks or
promotional videos, I often publish to YouTube for visibility and
discoverability. When embedding video content in my website, however, I
don’t like that option: it embeds Google code (and sends traffic to that
page to Google), and I also don’t have any control over the ads or the
post-video recommendations. This last point is especially important for
educational videos, because I wouldn’t want the recommender sending
viewers or students off to some other source I haven’t vetted and which
I may be specifically avoiding. Since my site is relatively low-traffic,
I use <a href="https://bunny.net/stream/">Bunny Stream</a> to host
embedded videos here. They don’t cost a lot, re-encode to a range of
quality levels, and their player is pretty decent.</p></li>
<li><p>If you really want high-quality background replacement, then a
good lighting setup and a green screen will produce better results than
using the “virtual background” feature of Camtasia or the
videoconference tool. However, for most academic purposes, it isn’t
worth the extra cost, space, and hassle in my opinion.</p></li>
</ul>
</section>
<section id="wrapup" class="level2">
<h2>Wrapup</h2>
<p>That’s… probably more than you were hoping to read, but I hope it is
helpful. Go out and make great videos!</p>
</section>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[2023 in Review]]></title>
        <id>https://md.ekstrandom.net/blog/2023/12/2023</id>
        <link href="https://md.ekstrandom.net/blog/2023/12/2023"/>
        <updated>2023-12-27T05:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="right autosize compact">
<a href="/images/unsplash-2023.jpg"><img srcset="/images/unsplash-2023-320.jpg 1x, /images/unsplash-2023-480.jpg 1.5x, /images/unsplash-2023-640.jpg 2x" src="/images/unsplash-2023-640.jpg" alt="Ominous green computer monitors. The center one says “2023 I'm Coming”"></a>
<figcaption class="credit">
Photo by
<a href="https://unsplash.com/@vertex_800?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Vertex
Designs</a> on
<a href="https://unsplash.com/photos/a-room-filled-with-lots-of-green-cubes-8pfj0HnI4Fo?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a>
</figcaption>
</figure>
<p>I’ve often closed out the year with two blog posts: my <a
href="/blog/2023/12/tools" title="2023 State of the Tools">tool
report</a>, and a year-in-review. It’s been a pretty full year!</p>
<p>One of the major accomplishments was moving to Philadelphia to take a
new position in the information science department at Drexel
University<a href="#fn1" class="footnote-ref" id="fnref1"
role="doc-noteref"><sup>1</sup></a>. I’m very excited for the coming
years here, as the interdisciplinary information science context is an
ideal home for my approach to research and scholarship. I did have to
give up tenure for the move, but I think it was worth it.</p>
<p>Beyond that, a few other good things have happened this year…</p>
<hr class="fold">
<section id="student-achievements" class="level2">
<h2>Student Achievements</h2>
<ul>
<li>My first Ph.D. student <a href="https://amifaraj.github.io"
class="advisee">Amifa Raj</a> successfully defended in June, and is now
an applied scientist at Microsoft.</li>
<li>My second Ph.D. student <a
href="https://www.linkedin.com/in/nihemelandu/" class="advisee"
title="Ngozi Ihemelandu on LinkedIn">Ngozi Ihemelandu</a> successfully
defended in December, and is now on the job market.</li>
<li><a href="https://www.linkedin.com/in/srabanti-guha-670836218/"
class="advisee" title="Srabanti Guha on LinkedIn">Srabanti Guha</a>
successfully completed her M.S. project and defense in May. Since Drexel
info sci’s M.S. programs are not research-based, she is probably my last
M.S. advisee for the foreseeable future.</li>
<li>Undergrad RA <span class="student">Christine Pinney</span>
first-authored and presented a <a href="/pubs/chiir-gender">full paper
at CHIIR 2023</a> on gender in IR research, in collaboration with Amifa,
Alex Hanna, and myself.</li>
<li><span class="advisee">Amifa</span>’s internship project (on which I
collaborated) published as a <a href="/pubs/sigir-queries">short paper
in SIGIR 2023</a>.</li>
<li><span class="advisee">Ngozi</span>’s work on statistical inference
for recommender system experiments published as a <a
href="/pubs/sigir-inference">short paper in SIGIR 2023</a>.</li>
<li><span class="advisee">Ngozi</span>’s work on candidate set sampling
in recsys evaluation published at <a href="/pubs/wi-sampled-eval">WI-IAT
2023</a>.</li>
<li><span class="advisee">Amifa</span>’s work on measuring fairness in
grid layouts <a href="/pubs/facctrec23-grids">presented at FAccTRec
2023</a>.</li>
<li><span class="advisee">Ngozi</span>’s paper on multiple comparison
correction in search &amp; recsys experiments was accepted as a <a
href="/pubs/ecir-mcp">short paper for ECIR 2024</a>.</li>
<li><span class="advisee">Amifa</span>’s paper on <a
href="/pubs/ecir-fair-grids">grid-aware reranking for fairness</a> was
accepted to the IR for Good track at ECIR 2024.</li>
<li>Preprint with <span class="advisee">Amifa</span> on <a
href="/pubs/unified-browsing-preprint">integrated browsing models for IR
metrics</a>.</li>
</ul>
</section>
<section id="other-research-achievements" class="level2">
<h2>Other Research Achievements</h2>
<ul>
<li>Got a new grant (w/ Joe Konstan, Bart Knijnenburg, Robin Burke, and
Ed Malthouse) to build a personalized news recommendation platform to
support user-facing research on recommender systems.</li>
<li>Published a short piece on <a
href="/pubs/interactions-mko">multi-party information seeking</a> with
Sole &amp; Katherine.</li>
<li>Published an article in <em>Transactions on Recommender Systems</em>
with Ben Carterette and Fernando Diaz on <a
href="/pubs/tors-distributions">distributional evaluation of recommender
systems</a>.</li>
<li>Collaborated on a demo (led by Tobias Vente and Joeran Beel) of an
<a href="/pubs/lenskit-auto">auto-tuning framework for LensKit</a>.</li>
<li>The many-authored paper on <a href="/pubs/recsys-values">values in
recommender systems</a> (led by Jonathan Stray) that I contributed to is
out in <em>Transactions on Recommender Systems</em>.</li>
<li>Contributed to a position piece led by Alexandra Olteanu on the need
for <a href="/pubs/rai-impact">impact statements in responsible AI
research</a>.</li>
<li>Our paper (with Sole Pera, Henriette Cramer, and Lex Beattie) on <a
href="/pubs/ecir-impacts">consumer impacts of IR systems</a> was
accepted to the IR for Good track at ECIR 2024.</li>
<li>Submitted several more papers that unfortunately were not
published.</li>
<li>Started a couple new collaborations that will hopefully produce
interesting results in the next year or three.</li>
<li>Gave a seminar talk for <a href="/talks/2023/raise">University of
Washington RAISE</a>.</li>
<li>Gave a seminar talk for <a href="/talks/2023/utais">UT
Austin</a>.</li>
<li>Gave a seminar talk for <a href="/talks/2023/glasgow">University of
Glasgow</a>.</li>
<li>Spoke (virtually) at an <a href="/talks/2023/ica">ICA
post-conference</a>.</li>
</ul>
<p>This year I made a total of 10 paper submissions and posted 2
preprints (not counting preprints of accepted material); we had 4
rejects to get our 6 conference publications (some of which were
resubmissions of work rejected last year)<a href="#fn2"
class="footnote-ref" id="fnref2"
role="doc-noteref"><sup>2</sup></a>.</p>
</section>
<section id="teaching" class="level2">
<h2>Teaching</h2>
<ul>
<li>Taught <a href="/teaching/recsys/">Recommender Systems</a> in spring
term, as my last class at Boise State.</li>
<li>Taught <a href="/files/teaching/info659-f23.pdf">INFO 659 (<em>Intro
to Data Analytics</em>)</a> for my first class at Drexel.</li>
<li>Begun preparations for teaching Drexel’s recommender systems class,
<a href="/files/teaching/dsci641-w24.pdf">DSCI 641</a>.</li>
</ul>
</section>
<section id="service-organization" class="level2">
<h2>Service &amp; Organization</h2>
<ul>
<li>Program committees: FAccT (AC), SIGIR, RecSys (SPC), ECIR.</li>
<li>Co-organized the <a
href="https://facctrec.github.io/facctrec2023/">6th FAccTRec
workshop</a>.</li>
<li>Numerous journal reviews.</li>
<li>2 international research proposal reviews.</li>
<li>Final year of FAccT executive committee service.</li>
<li>TheWebConf and SIGIR best paper committees.</li>
<li>Joined the editorial board for <em>Foundations and Trends in
Information Retrieval</em>.</li>
<li>Drexel IS Ph.D. committee (ongoing).</li>
<li>Drexel IS faculty search committee (ongoing).</li>
<li>Traveled to CHIIR (Austin, TX), SIGIR (Taipei), FAccT (Chicago), and
RecSys (Singapore).</li>
</ul>
</section>
<section id="wrapup" class="level2">
<h2>Wrapup</h2>
<p>I’m pretty sure I’ve probably forgotten a thing or two here. It’s <a
href="/blog/2017/02/productivity-self-care">helpful to me</a> to sit and
reflect on what I’ve gotten done in the course of a year. I’m especially
glad to have such a long section of student accomplishments to list. And
hopefully some reporting on rejection levels is helpful to others<a
href="#fn3" class="footnote-ref" id="fnref3"
role="doc-noteref"><sup>3</sup></a>.</p>
<p>And thank you very much to my excellent collaborators who have made
all of this possible.</p>
<p>For the next year, there are of course many new things in planning
(or progress), and I look forward to starting with some new
Ph.D. students at Drexel.</p>
</section>
<section id="footnotes" class="footnotes footnotes-end-of-document"
role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>The funnel for this search, if anyone is curious, was 9
applications (I was quite selective in where I applied this time), 2
phone screens, and 1 on-site that yielded the offer I accepted.<a
href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn2"><p>I have an Excel workbook in which I record all paper
submissions, grant submissions, etc., and their outcomes. Makes it
easier to prepare end-of-year review materials, and lets me see my
overall publication statistics.<a href="#fnref2" class="footnote-back"
role="doc-backlink">↩︎</a></p></li>
<li id="fn3"><p>I like the idea of a “CV of Failures”, and more
generally normalizing and documenting the amount of rejection we get in
academia. I don’t publish the list of exact failures and rejections
because much of that work is now under review elsewhere, but hopefully
seeing some of the numbers is at least helpful.<a href="#fnref3"
class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[2023 State of the Tools]]></title>
        <id>https://md.ekstrandom.net/blog/2023/12/tools</id>
        <link href="https://md.ekstrandom.net/blog/2023/12/tools"/>
        <updated>2023-12-26T05:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="autosize compact right">
<a href="/images/unsplash-office-tools.jpg"><img srcset="/images/unsplash-office-tools-320.jpg 1x, /images/unsplash-office-tools-480.jpg 1.5x, /images/unsplash-office-tools-640.jpg 2x" src="/images/unsplash-office-tools-640.jpg" alt="Several office tools arranged around paper and paper flags." class="webfeedsFeaturedVisual"></a>
<figcaption class="credit">
Photo by
<a href="https://unsplash.com/@dancristianpaduret?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Dan
Cristian Pădureț</a> on
<a href="https://unsplash.com/photos/assorted-color-plastic-tools-on-gray-wooden-table-noOXRT9gfQ8?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a>
</figcaption>
</figure>
<p>It’s time for another review of my current toolkit!</p>
<p>With a new job and a new city, I needed to re-assemble my work
computing setup from scratch and am now running MacBooks both at home
and work, so there are a number of changes. I also completely overhauled
our home network. Quite a few software things have stayed the same,
though.</p>
<hr class="fold">
<section id="endpoints" class="level2">
<h2>Endpoints</h2>
<p>I’m now primarily on Apple (MacBook Air M2 at home, 14” MBP w/ M2 Pro
for work). This is working well, with much better performance and
battery life than when I was on Windows (even on an i7 Surface
Laptop).</p>
<p>I also maintain a small Windows remote desktop machine at work for
software and class instruction testing, and got an iPad Air for reading
and paper markup. Plus our old Windows desktop I mostly use for
gaming.</p>
<figure class="autosize left compact">
<a href="/images/drexel-office-endpoint.jpg"><img srcset="/images/drexel-office-endpoint-320.jpg 1x, /images/drexel-office-endpoint-480.jpg 1.5x, /images/drexel-office-endpoint-640.jpg 2x" src="/images/drexel-office-endpoint-640.jpg" alt="Photo of my office setup."></a>
<figcaption>
University office compute setup (without CalDigit).
</figcaption>
</figure>
<p>I now use a <a
href="https://www.asus.com/us/displays-desktops/monitors/proart/proart-display-pa279cv/">27”
4K ASUS ProArt display</a>. It looks great, and also works as a docking
station if you don’t need a lot of ports or an ethernet connection. So
far, the single 27” monitor with the laptop open on a stand is working;
don’t have a pressing need to get a second full-size monitor.</p>
<p>I’m still using a <a
href="https://www.amazon.com/gp/product/B01936N73I/">Kensington Expert
Mouse</a>, now paired with a <a
href="https://www.keychron.com/products/keychron-k15-pro-alice-layout-qmk-via-wireless-custom-mechanical-keyboard">Keychron
K15 Pro</a> low-profile Alice keyboard. I’m liking the Keychron a lot;
it doesn’t have quite the slope of the Microsoft ergonomics I’ve been
using for the last several years, but it doesn’t seem to be triggering
RSI recurrence. For mobile, nothing has changed; still on iPhone and
Apple Watch, and don’t anticipate switching.</p>
<details class="notes">
<summary>
Keyboard Configuration Notes
</summary>
<p>Since the Keychron is a third-party keyboard, it has two important
limitations on Mac: it can’t send a Mac “globe” keycode, and it doesn’t
support Touch ID. There isn’t a way to fix the Touch ID situation, but
since I don’t use Caps Lock, thanks to <a
href="https://github.com/qmk/qmk_firmware/issues/16651#issuecomment-1436093183">this
comment</a> I do have a fix for the globe key issue, which is the
easiest way to enter emojis.</p>
<ul>
<li>In <a href="https://usevia.app">Via</a>, the config tool used by
Keychron keyboards, change the following mappings:
<ul>
<li>map Caps Lock to Control (much more useful IMO)</li>
<li>map a macro key (I use M1) to Caps Lock</li>
</ul></li>
<li>In the modifier key settings in macOS, set Caps Lock to work as the
Globe key</li>
</ul>
<p>With this, I can hit M1 on the Keychron to enter an emoji or special
character. I also have a few other keymaps set (photo above was taken
before setting them up):</p>
<ul>
<li>Lock key sequence on one of the macro keys</li>
<li>Move Del above Delete</li>
<li>Map previous Del to Home, and other keys, so the top right column is
Home / PgUp / PgDn / End.</li>
<li>Pressing the rotary encoder is F13 (which mutes Zoom), so I have an
easy-access zoom mute button with my volume control.</li>
</ul>
</details>
<p>At work, I also use a CalDigit TS4 docking station to have some extra
ports and Ethernet, and have a 4TB Crucial SSD for expanded storage on
my MacBook.</p>
</section>
<section id="video-recording-conferencing" class="level2">
<h2>Video Recording &amp; Conferencing</h2>
<p>I didn’t feel like I was getting a lot of value out of the Logitech
Brio’s extra resolution, so when setting up at Drexel I got a C920s
instead. It is good enough, but not great; I think the Anker PowerConf
C200 I have at home is better.</p>
<p>For audio, I’m very pleased with my new setup: a <a
href="https://www.shure.com/en-US/products/microphones/mv7x?variant=MV7X">Shure
MV7X</a> connected to a <a
href="https://us.focusrite.com/vocaster">Focusrite Vocaster</a> USB
interface, with <a
href="https://www.sennheiser-hearing.com/en-US/p/hd-600/">Sennheiser HD
600</a> for listening. Call and recording audio quality is outstanding,
and the headphones sound very good both for calls and music.</p>
<p>I’m still using Camtasia for recording and editing lecture videos. I
have added OBS to my toolkit, along with the <a
href="https://apps.apple.com/us/app/camera-for-obs-studio/id1352834008">iPhone
app</a>, for capture from my phone camera when I need another camera
(e.g. for a document camera). Another useful addition has been <a
href="https://goodsnooze.gumroad.com/l/macwhisper"
class="entity">MacWhisper</a> for generating video captions. Its speech
recognition accuracy and segmenting logic are both very good, and it is
significantly less expensive than Audiate (although without the
text-based video editing capabilities).</p>
<details class="notes">
<summary>
Recommendations
</summary>
I’m very pleased with my microphone setup, but usually still recommend
the Blue Yeti, especially in the Yeticaster configuration, to most
people looking to upgrade their Zoom audio quality. The Yeti is very
good at significantly lower cost than a Shure setup, although it is
heavier and bulkier. If you do want the Shure, the regular MV7 connects
directly to USB without needing a second interface. I wanted a greater
degree of control and decoupling in my own setup, as well as the
hardware controls of the USB interface.
</details>
</section>
<section id="interactive-software" class="level2">
<h2>Interactive Software</h2>
<p>My general interactive computing has seen some changes, due to moving
to macOS and a new university that doesn’t use GSuite. I have a
subscription to <a href="https://setapp.com/">SetApp</a>, which yields a
number of useful utilities.</p>
<section id="browsing" class="level3">
<h3>Browsing</h3>
<p>Primarily browsing with <span class="entity">Firefox</span>; keep
Chrome around for Google apps, and use Edge on my home laptop for
accessing university things (Edge logged in to my Drexel account is a
decent “quick check work email” experience).</p>
<p>I’m now using <a href="https://netnewswire.com/"
class="entity">NetNewsWire</a> as my RSS reader, because Feedly did
something or another that made me pretty uninterested in continuing to
use their service. I don’t currently have a good bookmarking service,
since Pinboard is in a very low-maintenance mode and its creator decided
that defending JK Rowling would be a good use of his influence.</p>
</section>
<section id="reading-and-writing" class="level3">
<h3>Reading and Writing</h3>
<p>I still use <span class="entity">Office</span> for most of my general
writing, spreadsheets, and presentations, more so now that I’m at
Drexel, where we have Microsoft 365 instead of GSuite. I still use <span
class="entity">Google Docs</span> for a lot of collaborative documents,
since it’s the de facto standard for academic collaboration, and <span
class="entity">Overleaf</span> for authoring LaTeX. I’ve also started
using <a href="https://www.hogbaysoftware.com/" class="entity">Bike
Outliner</a> for note-taking, and may start using it to outline
drafts.</p>
<p>I’ve switched back to <a href="https://zotero.org"
class="entity">Zotero</a>, which I used during my Ph.D. and the couple
years after. I liked PaperPile, but it is tied to Google accounts and
requires Chrome.</p>
<p>For the last few months I’ve been using <a
href="https://highlightsapp.net/" class="entity">Highlights</a> as my
default PDF reader on both Mac and iPad, including for markup of student
work; it doesn’t allow freehand drawing like Drawboard, which I miss
sometimes, but it works well enough. I use <span class="entity">Acrobat
Pro</span> for last-stage preparation and touchup of PDFs.</p>
<p>Switching to Mac has introduced a weird element to my document
production workflow — Word now requires Microsoft cloud services to
create workable PDFs (with tags, outlines, etc.) and behaves quite badly
with fonts, and Acrobat Pro on Mac similarly does a very poor job and
requires cloud services (also with font problems). Acrobat Pro for
Windows still provides full-featured local PDF creation, so when I need
to convert a Word document to PDF for class, I usually open it on my
Windows remote machine and use Acrobat there.</p>
</section>
<section id="general-productivity" class="level3">
<h3>General Productivity</h3>
<p>I’m trying out <a href="https://www.hogbaysoftware.com"
class="entity">Bike Outliner</a> for general note-taking; it’s better
than other non-cloud things I’ve tried, but the jury is still out. I’m
also now using <a href="https://www.taskpaper.com/"
class="entity">Taskpaper</a> to track work to-dos and my <a
href="/blog/2021/02/runway">runway document</a>. I’m not entirely happy
with it, but Bike doesn’t have good filtering capabilities, and the
space of local-first infinite outliners or task managers that aren’t
org-mode is rather thin. I’ve augmented it with a few user scripts and a
custom style sheet, along with a command-line tool to print upcoming
tasks across multiple Taskpaper files.</p>
<p>For better or worse, I’m now using <span
class="entity">Outlook</span> for my e-mail and calendaring; it has
definitely taken some acclamation, and on net I do prefer Gmail.
Unfortunately, Drexel also disables IMAP, so if I want to write some
scripts to fill in holes (which I’ve done before), I’ll need to do it
with the Microsoft Graph API.</p>
<p>When I first started my new position, I tried out OneNote and To Do
to see if they might meet my note-taking and task management needs, but
To Do doesn’t allow the level of automation and fluid keyboard operation
that I need.</p>
<p>At home, I use the Apple apps for email, calendar, and tasks, with
our email on <a href="https://fastmail.com" class="entity">Fastmail</a>
and other things on <span class="entity">iCloud</span>.</p>
</section>
<section id="system-utilities" class="level3">
<h3>System Utilities</h3>
<p>There are a number of tools I use to make my day-to-day system usage
better:</p>
<ul>
<li><p><a href="https://commander-one.com/" class="entity">Commander
One</a> for file management, when I’m not just using Finder. I’ve also
used <a href="https://marta.sh" class="entity">Marta</a>, but Commander
One looks like a better option in the long term.</p></li>
<li><p><a href="https://iterm2.com/" class="entity">iTerm 2</a> as my
terminal, with <a href="https://sourcefoundry.org/hack/"
class="entity">Hack</a> as my terminal font. I’m very intrigued by <a
href="https://monaspace.githubnext.com/" class="entity">Monaspace</a>
and am trying it out.</p></li>
<li><p><a href="https://soulver.app/" class="entity">Soulver</a> is a
ridiculously useful little program for general calculations, including
things I would have grabbed Excel for before.</p></li>
<li><p><a href="https://www.macbartender.com/Bartender5/"
class="entity">Bartender</a> to wrangle my menu bar icons.</p></li>
<li><p><a href="https://bjango.com/mac/istatmenus/" class="entity">iStat
Menus</a> for more useful status displays in the menu bar.</p></li>
<li><p><a href="https://github.com/maxgoedjen/secretive"
class="entity">Secretive</a> to store SSH authentication keys in Apple’s
secure enclave.</p></li>
<li><p><span class="entity">1Password</span> for password
management.</p></li>
<li><p>I just discovered <a href="https://pasteapp.io/"
class="entity">Paste</a> and will be trying it as a clipboard
manager.</p></li>
</ul>
</section>
<section id="mobile" class="level3">
<h3>Mobile</h3>
<p>I also run quite a few different apps on my phone; a few that stand
out:</p>
<ul>
<li><span class="entity">OTP Auth</span> for 2FA.</li>
<li><span class="entity">RPN48</span> for my primary calculator.</li>
<li><span class="entity">Signal</span> (preferred) and <span
class="entity">WhatsApp</span> for secure messaging.</li>
<li><span class="entity">RideWithGPS</span> for bike ride logging.</li>
<li><span class="entity">Pixelmator</span> to edit images.</li>
<li><span class="entity">ImgPlay</span> for GIF editing.</li>
<li><span class="entity">UniChar</span> to access a wider range of
Unicode characters.</li>
<li>iPhone companions to much of the Mac software I use, such as
NetNewsWire and Banktivity.</li>
</ul>
</section>
</section>
<section id="cli" class="level2">
<h2>Command-Line Software</h2>
<p>A lot of my computing is in the terminal. I tried <span
class="entity">fish</span> for a while this year, but am now back on
<span class="entity">zsh</span>. I’ve been trying <a
href="https://ohmyz.sh/" class="entity">oh-my-zsh</a>, but I’m not sure
about some of its configuration choices yet. Several augmentation
utilities fill out my shell profile:</p>
<ul>
<li><a href="https://starship.rs/" class="entity">starship</a> for my
prompt.</li>
<li><a href="https://direnv.net/" class="entity">direnv</a> for managing
per-project environment variables (including actiating a project’s Conda
environment).</li>
<li><a href="https://github.com/ajeetdsouza/zoxide"
class="entity">zoxide</a> to navigate directories.</li>
</ul>
<p>Beyond that, a very non-exhaustive list of tools I find useful:</p>
<ul>
<li><span class="entity">nano</span> for terminal text editing.</li>
<li><a href="https://github.com/sharkdp/fd" class="entity">fd</a> to
search directory trees.</li>
<li><a href="https://github.com/eza-community/eza"
class="entity">eza</a> (successor to <code>exa</code>) for file listing.
My terminal config aliases <code>l</code> to <code>eza</code> and
<code>ll</code> to <code>eza -l</code>.</li>
<li><a href="https://github.com/sharkdp/bat" class="entity">bat</a> to
view text &amp; code files with syntax highlighting.</li>
<li><a href="https://github.com/BurntSushi/ripgrep"
class="entity">ripgrep</a> to search file content.</li>
<li><a href="https://github.com/bootandy/dust" class="entity">dust</a>
and <a href="https://dev.yorhel.nl/ncdu" class="entity">ncdu</a> for
analyzing disk usage.</li>
<li><a href="https://jless.io/" class="entity">jless</a> to view JSON
files.</li>
<li><a href="https://github.com/tmux/tmux" class="entity">tmux</a> for
terminal multiplexing.</li>
<li><a href="https://github.com/watchexec/watchexec"
class="entity">watchexec</a> to auto-rerun things on file changes
(e.g. continuously rebuilding my website while working on it).</li>
<li><a href="https://age-encryption.org/" class="entity">age</a> for
file encryption.</li>
<li><a href="https://jedisct1.github.io/minisign/"
class="entity">minisign</a> when I need cryptographic signatures.</li>
<li><a href="https://github.com/XAMPPRocky/tokei"
class="entity">tokei</a> to count source code.</li>
</ul>
</section>
<section id="data-management" class="level2">
<h2>Data Management</h2>
<p>I’ve made some significant changes in my data management across the
board this last year, so that seems to merit a section of its own.</p>
<p>I’m now storing university data on <span
class="entity">OneDrive</span>, as that’s what Drexel provides. I also
use OneDrive for most of my data syncing between my MacBook and iPad,
although I share some files with iCloud. Each account has 5TB available,
so it works pretty well. At home, I mostly use <span
class="entity">iCloud</span>, and use an iCloud shared folder to share
files between work and personal computers (I use separate Apple accounts
for my home and work machines). I also store all photos in iCloud, and
use shared albums to send files between my phone and university
MacBook.</p>
<p>I use <span class="entity">git</span> for all of my source code
storage and version management. To have greater control over when and
where data files are synchronized and stored, I’ve started using <a
href="https://git-annex.branchable.com/" class="entity">git-annex</a>
for data archival (as well as the assets for my website). I still use
<span class="entity">syncthing</span> for a few folders, but not as much
as I used to.</p>
<p>We’ve switched our home endpoint backup from Backblaze to <a
href="https://kopia.io/" class="entity">Kopia</a>, to have them more
under our control and to manage costs as Backblaze pricing has
increased. It’s configured to store backups on our NAS, which
synchronizes them to cloud storage regularly.</p>
<details class="notes">
<summary>
Recommendations
</summary>
Kopia is working well for us, with our requirements and infrastructure,
but I still recommend Backblaze for most people, as it’s dead-easy to
set up.
</details>
<p>I’m still using <a href="https://dvc.org" class="entity">dvc</a> for
experiment data. Recent versions have introduced some annoying
regressions, but I don’t have a better solution yet.</p>
</section>
<section id="programming-and-data-analysis" class="level2">
<h2>Programming and Data Analysis</h2>
<p><span class="entity">Visual Studio Code</span> is still my primary
programming editor across all languages I use.</p>
<p>I’m still using <span class="entity">Python</span> and <span
class="entity">Rust</span> as my primary research languages. I’ve
started dabbling in <span class="entity">R</span> again, as we use it in
the class I taught this fall; I’m not sure long-term if what the
R/Python balance will be for statistical analysis and visualization.
I’ve also started using <a href="https://quarto.org/"
class="entity">Quarto</a> in place of Jupyter in some projects, and for
rendering my <a href="https://bookdata.piret.info">data tools
documentation</a>.</p>
<p>The <a href="https://pola-rs.github.io/polars/">Polars</a> library
has been a useful addition for data processing, both in Python and Rust;
it’s replaced Pandas for larger-scale data processing.</p>
<p>I’ve started using more <span class="entity">TypeScript</span> in my
JavaScript programming, and that’s been quite useful; my website is <a
href="/colophon">entirely TypeScript</a> now. TypeScript’s type system
is one of the more expressive I’ve used, which has been fun, but it also
has several escape hatches that turn the guarantees one would usually
expect from a typechecker into more of set of best-effort guidelines. It
still gives me higher confidence in code correctness. I expect a lot of
my JavaScript work will be in TypeScript going forward, mostly with
<span class="entity">Deno</span> when not in the browser.</p>
<p>Finally (on the general-purpose front), I’ve also been writing more
<span class="entity">Tcl</span> in various places. I find it severely
underappreciated and quite like it as a glue and scripting language, and
the <a href="https://jim.tcl.tk/">Jim interpreter</a> makes it highly
portable.</p>
<p>As noted above, I’m still using <span class="entity">DVC</span> for
research data management and analysis pipeline automation. To work
around the limitations of DVC’s pipeline language, I’ve moved away from
the templates and weird TCL scripts I’ve experimented with in the past
and now use <a href="https://jsonnet.org/" class="entity">jsonnet</a> to
generate non-trivial pipelines (see the <a
href="https://github.com/PIReTship/bookdata-tools">Book Data Tools</a>
for an example).</p>
<p>One significant change in my programming across languages is that I
have started using autoformatters when they are available. For Rust,
this is the standard <code>rust fmt</code>; for Python, I’m using <a
href="https://docs.astral.sh/ruff/" class="entity">Ruff</a> (it’s also
replaced flake8 as my primary linter); and for TypeScript &amp;
JavaScript, I’m using <code>deno fmt</code>. I’m also using either
Prettier or formatters built in to VS Code extensions for other
languages like yaml and jsonnet.</p>
<p>I’m still using <span class="entity">Conda</span> — usually with
conda-forge — for installing research software and Python development
dependencies. It remains the best solution I’ve found for reproducible,
cross-platform software environment installation.</p>
<p>Now that I’m back on Mac, I’m using <a href="https://kapeli.com/dash"
class="entity">Dash</a> again for reading documentation, although I
still do quite a bit of that in the browser.</p>
</section>
<section id="other-software-services" class="level2">
<h2>Other Software &amp; Services</h2>
<p>In no particular order:</p>
<ul>
<li><span class="entity">Banktivity</span> for personal finance
tracking.</li>
<li><a href="https://unsplash.com" class="entity">Unsplash</a> and <a
href="https://thenounproject.com" class="entity">The Noun Project</a> to
source images.</li>
<li>A number of other things that don’t come to mind right now.</li>
<li><a href="https://typefaceapp.com/" class="entity">Typeface</a> for
selecting fonts.</li>
</ul>
</section>
<section id="university-compute" class="level2">
<h2>University Compute</h2>
<p>I’m still getting my university compute needs and resources figured
out, but I’ve started with one server node in our data center, with dual
64-core Epyc 7662, 256GB RAM, and an Nvidia A40 GPU.</p>
</section>
<section id="home-network" class="level2">
<h2>Home Network</h2>
<p>I completely overhauled our home network this year to get better
Wi-Fi, upgraded network storage, and a firewall that hasn’t taken up
conscientious objection to software updates. I’ve also standardized on
Debian Stable across our servers.</p>
<p>It was time to replace our old QNAP NAS; its Atom CPU was old enough
that it didn’t have hardware-accelerated crypto, and was having
increasing trouble keeping up with the amount of data I wanted to run
through it. I replaced it this year with a <a
href="https://www.pine64.org/rockpro64/">RockPro64</a> in Pine’s <a
href="https://pine64.com/product/rockpro64-metal-desktop-nas-casing/">NAS
case</a>, with 2 8TB Toshiba hard drives. This machine runs Debian
Stable, with syncthing, Samba, and git-annex for its primary data
transfer roles. It also serves as our primary backup host with Kopia
Backup.</p>
<details class="notes">
<summary>
NAS Configuration Details
</summary>
<p>Hardware:</p>
<ul>
<li><a href="https://www.pine64.org/rockpro64/">RockPro64</a> board
(RK3366 hex-core ARM CPU)</li>
<li><a
href="https://pine64.com/product/rockpro64-metal-desktop-nas-casing/">Pine
NAS case</a></li>
<li><a href="https://www.amazon.com/gp/product/B07SWHRXFS/">IO CREST
SI-PEX40138</a> PCIe/SATA adapter (4x SATA ports + M.2 SATA slot)</li>
<li>2x Toshiba N300 8TB NAS HDDs</li>
<li>Transcend 128GB M.2 SATA SSD (scratch space)</li>
<li>Pine eMMC module (boot drive)</li>
<li>Mid-profile heat sink</li>
</ul>
After some experimentation, I settled on formatting the primary storage
with XFS on top of LVM RAID1, with the physical volumes encrypted on top
of an integrity layer (using <code>cryptsetup</code>’s built-in
integrity configuration support, so the integrity is a cryptographic
MAC). ZFS is nice, but doesn’t use the kernel’s crypto accelerator
support and doesn’t use Arm’s AES extensions on its own.
</details>
<p>I also bought an <a
href="https://www.friendlyelec.com/index.php?route=product/product&amp;product_id=290">R5C</a>
to serve as our firewall &amp; router. This little box has an RK3568 SOC
and 2 2.5Gbps Ethernet ports on the PCIe bus. It’s also running Debian
stable, and thanks to <a
href="https://github.com/inindev/nanopi-r5/tree/main">inindev’s
images</a> is able to run a stock Debian install instead of Friendly’s
images with difficult-to-update kernels. A 5-port switch and a TP-Link
<a
href="https://www.amazon.com/gp/product/B0BGJJWPWC/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&amp;psc=1">TL-WA3001</a>
Wifi6 router complete our home network.</p>
<p>I’m using <a href="https://tailscale.com/">Tailscale</a> for VPN
connections between home, cloud, and my devices when on the move. It
works great, and is much easier than managing Wireguard myself (or
messing with our old router’s OpenVPN support). I also use a <a
href="https://tornadovps.com/">Tornado VPS</a> server to host my website
(behind Bunny CDN) and handle other coordination between home and remote
devices.</p>
</section>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Multiprocessing Woes]]></title>
        <id>https://md.ekstrandom.net/blog/2023/11/multiprocessing</id>
        <link href="https://md.ekstrandom.net/blog/2023/11/multiprocessing"/>
        <updated>2023-11-10T05:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="autosize compact right">
<a href="/images/bicycle-race.jpg"><img srcset="/images/bicycle-race-320.jpg 1x, /images/bicycle-race-480.jpg 1.5x, /images/bicycle-race-640.jpg 2x" src="/images/bicycle-race-640.jpg" alt="A peloton in a bicycle race, moving quickly in parallel."></a>
<figcaption class="credits">
Photo by
<a href="https://unsplash.com/@markusspiske?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Markus
Spiske</a> on
<a href="https://unsplash.com/photos/group-of-cyclist-on-asphalt-road-WUehAgqO5hE?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a>
</figcaption>
</figure>
<aside class="tldr">
<strong>TLDR:</strong> LensKit is going to keep using a custom layer on
top of Python’s <code>ProcessPoolExecutor</code>, but moving that out
into a separate package.
</aside>
<p>It is a truth universally acknowledged that parallel computing on
Python is an experience that ranks somewhere alongside “facial
exfoliation with a rusted cheese grater” in its pleasantness.</p>
<p>In this post I’m going to briefly review our particular (common!)
problem, the state of multiprocessing in Python, and why it
unfortunately appears necessary to continue using my own parallel
processing layer in LensKit.</p>
<section id="shape-of-the-problem" class="level2">
<h2>Shape of the Problem</h2>
<p>LensKit supports parallelism in two different places: model training
and batch evaluation. Model training uses the threading support of the
underlying compute engine (BLAS and Numba, although I’m looking to get
away from Numba). Batch evaluation uses the Python standard library’s <a
href="https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ProcessPoolExecutor"><code>ProcessPoolExecutor</code></a>
through a custom wrapper layer (more on this later).</p>
<p>In batch evaluation, we have a problem that is common to many machine
learning batch “inference”<a href="#fn1" class="footnote-ref"
id="fnref1" role="doc-noteref"><sup>1</sup></a> problems: we have a
large model object that is read-only (except for ephemeral caches), and
multiple threads or processes need to compute results using this model
object (in our case, computing recommendations for the thousands or
millions of test users). Since this model is large, it presents two
constraints:</p>
<ul>
<li>We only want one copy in memory, no matter how many workers we use
(if we eventually support multi-node evaluation, we would want one copy
per node).</li>
<li>We don’t want to have to re-serialize it for each task (user).</li>
</ul>
</section>
<section id="usual-api-design" class="level2">
<h2>Usual API design</h2>
<p>Typical Python parallel processing APIs are designed around
submitting tasks with arguments to the executor pool. At a surface
level, our use case maps reasonably well to the typical <code>map</code>
function provided by e.g. <a
href="https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.Executor.map"><code>Executor</code></a>:</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> <span class="bu">map</span>(func, <span class="op">*</span>iterables):</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>    <span class="co"># apply func to items in zip(iterables), in parallel</span></span></code></pre></div>
<p>Most Python parallel APIs provide a function like this, and differ
primarily in their implementation details and optimizations. However,
there is one element of this API that does not match our use case: the
function needs access to the large shared model object in addition to
the per-task arguments it gets from <code>iterables</code>.</p>
</section>
<section id="current-solution" class="level2">
<h2>Current Solution</h2>
<p>The <code>ProcessPoolExecutor</code> works on top of <a
href="https://docs.python.org/3/library/multiprocessing.html#multiprocessing.pool.Pool"><code>multiprocessing.Pool</code></a>,
which allows us to provide a worker setup function that will be called
to instantiate each worker thread, as well as provide arguments to the
setup function. This conceptually maps quite well: if we have a way to
put a model in shared memory, we can use worker setup functions to store
it in a global variable, and then supply a task function to
<code>map</code> that fetches the shared model and passes it, along with
the per-task arguments, to the batch evaluation function.</p>
<p>In Python 3.8, the <code>pickle</code> module gained support for the
new Protocol 5 pickling format, which allows application code to provide
a custom callback for serializing anything that implements the Python
<code>buffer</code> API, which includes the NumPy arrays that make up
the bulk of our model’s saved data. If we “serialize” those by copying
them to shared memory, then the remaining object structure is a
relatively small pickle. The <a
href="https://binpickle.lenskit.org">binpickle</a> library leverages
this to provide an on-disk serialization format that supports
memory-mapping buffer contents.</p>
<p>I created LensKit’s <a
href="https://lkpy.lenskit.org/en/0.14.2/parallel.html">Invoker</a>
framework to abstract all of this this. Its <code>ModelOpInvoker</code>
implementations take a model object, and do the following:</p>
<ul>
<li>Serialize the model to shared memory (if it isn’t already
serialized). It supports both Python’s <code>SharedMemory</code> and <a
href="https://binpickle.lenskit.org">binpickle</a>, which was created
for the purpose and can be used to share through memory-mapped disk
files if <code>/dev/shm</code> has insufficient space for the shared
memory.</li>
<li>Deserialize the model in the worker processes, using shared memory
for the buffers.</li>
<li>Wire up logging so that log messages are properly passed back to the
lead process.</li>
<li>Provide the model object to a “model op”, a function that takes the
model and the task (e.g. user ID) and returns the results.</li>
</ul>
<p>It works pretty well, all things considered. There are a few
outstanding problems, though:</p>
<ul>
<li>I’m maintaining a parallel processing library.</li>
<li>Naïve use still has 2 copies of the model in memory, because we
cannot <em>move</em> the model’s buffers, we can only <em>copy</em>
them. I have workarounds, but they’re not pretty, and require
<code>binpickle</code> (basically, serializing to a memory-mappable
binpickle doesn’t require the output to be in memory, so you serialize
to disk, drop the original model object, and then have the invoker use
the pre-serialized model, which its’ capable of doing).</li>
<li>Excessive worker process logging can overwhelm the logging
pipe.</li>
</ul>
<p>So I spent some time this week trying to see if someone else has
built a good solution I can use instead of continuing to maintain the
invoker library.</p>
</section>
<section id="current-status" class="level2">
<h2>Current Status</h2>
<p>So far as I can tell, there really hasn’t been a lot of movement
towards a solution for this specific problem shape. I also may not be
looking in the right places, but I see surprisingly little uptake of
Pickle 5’s ability to facilitate shared memory.</p>
<p>The things I’ve looked at in my current exploration include:</p>
<ul>
<li><a
href="https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ProcessPoolExecutor"><code>ProcessPoolExecutor</code></a>,
didn’t expect anything new here, and it’s what we’re already using.</li>
<li><a href="https://joblib.readthedocs.io">joblib</a>, which I used in
an earlier version of LensKit. Joblib focuses on providing a very clean
and robust parallel implementation of <code>map</code>, and admits that
this comes at the expense of other functionality; there is no way to
provide per-worker model data (at least not without configuring your own
executor). It uses <code>loky</code> and <code>cloudpickle</code> to
work around old bugs in Python’s concurrency library, improve
performance, and increase the number of things that can be pickled; the
relevant Python concurrency bug has been fixed in the versions of Python
I support, and I haven’t found <code>pickle</code>’s limitations to be a
problem in our code paths. Joblib also provides
<code>dump</code>/<code>load</code> routines that allow memory-mapped
Numpy arrays, but it’s a hacky solution that predates Pickle 5.
<code>binpickle</code> was born from “what if we did Joblib’s dump/load
but on top of Pickle 5?”. For my purposes in LensKit, joblib doesn’t
provide much value on top of Python’s standard library, so it isn’t
worth the dependency.</li>
<li><a href="https://sybrenjansen.github.io/mpire/">mpire</a> supports
large shared models, but it does so through <code>fork</code>, so that
feature doesn’t work on Windows (or with any other multiprocessing spawn
model). Good Windows support is a priority for LensKit.</li>
<li><a href="https://ipyparallel.readthedocs.io/">ipyparallel</a> claims
support for “zero-copy” buffers, but the devil is in the details. This
zero-copy support only works when you actually pass the NumPy array to
the <code>map</code> or <code>push</code> function, not when it is an
implementation data structure of an object you pass (with a few
exceptions). The serialization layer is extensible, so I could use
Pickle5 to serialize anything wtih zero-copy buffers, but it gets to the
next problem: the zero copies are in the process of preparing the data
to send, but as near as I can tell, it is still copied over the zeromq
pipe. Therefore it isn’t zero-copy in the sense that I need, but rather
1 (instead of 2) copies in the driver process and one copy in each of
the worker processes. Supporting true shared model objects still
requires the same serialization logic I use to work with the standard
library.</li>
<li><code>dask.distributed</code> and <code>ray</code> are both
interesting, but very much seem to want you to work within their
framework. LensKit is designed to fit with whatever tools the user wants
to use, particularly fro model implementation, so forcing evaluation
into a heavier framework like them would be a paradigm change. It also
appears like I would still need the custom shared memory serialization
logic, although that may not be entirely correct. I also just haven’t
had good success getting actual speedups from Dask when I have tried it
in the past.</li>
<li><code>torch.multiprocessing</code> <em>does</em> go there, for
PyTorch tensors — when you send an object to a worker, it moves the
tensor into shared memory and allows the workers to access it. Only
works for PyTorch, not numpy or other buffer-based APIs.</li>
</ul>
<p>That’s what I’ve been able to learn so far. Multiple operations with
single large shared model object just does not seem to be a
well-supported pattern outside of PyTorch and heavyweight cloud- and
cluster-oriented frameworks. With LensKit’s goals of good single-node
performance and minimal constraints on users’ environments, there
doesn’t seem to be a better solution than what I’m doing now.</p>
</section>
<section id="sidebar-subinterpreters" class="level2">
<h2>Sidebar: Subinterpreters</h2>
<p>Python recently added “subinterpreters”, which are multiple isolated
Python interpreters in a single process, and Python 3.12 allows each
subinterpreter to have its own global interpreter lock. This seems to
open the door for a very good solution: parallelize with threads, giving
each thread its own subinterpreter; share the buffer memory between
subinterpreters and keep using pickle for the object structures and
other inter-thread communication. However, there is significant work to
upgrade extension modules to support subinterpreters, and it <a
href="https://github.com/numpy/numpy/issues/24755">doesn’t look like a
priority</a> for NumPy yet.</p>
</section>
<section id="moving-forward" class="level2">
<h2>Moving Forward</h2>
<p>It looks like I need to keep maintaining a parallel compute API for
large shared model objects in order to enable LensKit’s parallel
evaluation. I’m looking to make a few changes to make it more usable and
ergonomic, and encourage adoption (and co-maintenance?) by others:</p>
<ul>
<li>Create a new library for the invoker code so it can be maintained
separately and used by non-LensKit projects. It will continue to support
both Python shared memory and <code>binpickle</code> mapping, depending
on the use case. I may also consider adding support for
<code>plasma</code>, but I’m not sure that would gain us much on top of
Python’s shared memory API.</li>
<li>Add support to Binpickle to deserialize directly to shared memory,
so users don’t need to have separate compressed BinPickle files for
model storage and mappable ones for parallelism, unless they
specifically need the increased memory capacity afforded by BinPickle
mapping.</li>
<li>Add progress bar support.</li>
<li>Add optional “destructive pickling” that tries to dismantle NumPy
arrays (by resizing them to 0) once their bytes have been transferred,
to enable “move to shared memory” semantics. This is possible because
the buffer callback API in Pickle 5 makes the buffer’s owner available.
Destructive pickling will leave objects in unusable states and must be
used with care, but it seems worth trying for the common cases of models
that will not be used after they have been serialized.</li>
<li>Eventually: consider adding support for <a
href="https://ipyparallel.readthedocs.io/">ipyparallel</a>, with the
same custom deserialization logic currently used for process pool
executors. When used with memory-mappable <code>binpickle</code> files
stored on NFS, this will allow multi-node parallelism with one model
copy per node.</li>
</ul>
<p>I’ll update this post with links once the new library is up and
available. Hopefully people will find it useful!</p>
</section>
<section id="footnotes" class="footnotes footnotes-end-of-document"
role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>Inference is a very questionable name for applying a
model at runtime, because it makes it much more difficult to talk about
the difference between prediction and inference tasks in a statistical
sense. It seems to be the term we’re stuck with, but I hate it, and wish
that machine learning would be less gratuituous with redefining
already-established terms. I could also go on a rant about calling
interpolation weights “attention” when “interpolation weights” is
<em>right there</em>, especially when you’re trying to do information
retrieval or recommendation research that involves user attention.<a
href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Final Paper Checklist]]></title>
        <id>https://md.ekstrandom.net/blog/2023/08/final-paper-checklist</id>
        <link href="https://md.ekstrandom.net/blog/2023/08/final-paper-checklist"/>
        <updated>2023-08-10T04:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="compact autosize right">
<a href="/images/color-press.jpg"><img srcset="/images/color-press-320.jpg 1x, /images/color-press-480.jpg 1.5x, /images/color-press-640.jpg 2x" src="/images/color-press-640.jpg" alt="An old color printing machine, transferring a color image to paper." class="webfeedsFeaturedVisual"></a>
<figcaption class="credit">
Photo by
<a href="https://unsplash.com/@cedriksk?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Cedric
Verstraete</a> on
<a href="https://unsplash.com/photos/soZdTsmV8k8?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>
</figcaption>
</figure>
<p>There are a number of steps in preparing the final (or
“camera-ready”) version of a paper for publication. This post attempts
to document them. Many of these points are about making the paper
consistent with itself, so that it doesn’t look sloppy. This list
probably isn’t complete, but hopefully it’s a helpful start.</p>
<section id="proofreading" class="level2 checklist">
<h2 class="checklist">Proofreading</h2>
<ul>
<li><p>Correct errors noted in reviews.</p></li>
<li><p>Carefully proofread for spelling, grammar, unclear language, and
unhelpful redundancy. Make sure tense and pluralization are
appropriately consistent in all sentences.</p></li>
<li><p>Check that figure and table references are consistent. Specific
points:</p>
<ul>
<li>“Fig.” vs. “Figure” and “Table” vs. “Tbl.”; can mix and match
e.g. “Fig.” and “Table”, but not “Fig.” and “Figure”.</li>
<li>Capitalization — when a reference is in the middle of a sentence, is
the label (e.g. “Fig.”) capitalized? (At the beginning of a sentence, it
should always be capitalized.)</li>
<li>Some journals or publishers have a specific style regarding how
figures and tables are referenced: follow that if applicable. Springer
is one such publisher.</li>
<li>If the publication venue does not specify a style, be consistent
within the document. Look at a few recent papers to see what other
authors usually do.</li>
<li>Typical style for ACM publications is to use “Fig.” and “Table”, and
to always capitalize.</li>
</ul></li>
<li><p>When using LaTeX to render the final version: edit paragraphs so
we don’t have last lines with only 1–2 words.</p></li>
<li><p>Section headings should all be in title case. When a style uses
all-caps for some headings, it is better to write them in title case and
let the style code convert them to all-caps for display (LaTeX, Word,
and CSS are all capable of this); that way, the PDF bookmarks, table of
contents (when present), and similar navigational aids display them in
title case.</p></li>
<li><p>Figure and table captions should be consistent in terms of how
they are written.</p>
<ul>
<li>Are they written as short headings or as complete sentences with a
period?</li>
<li>Do they use title case or sentence case? (complete-sentence captions
should only use sentence case.)</li>
</ul>
<p>Be consistent <em>within</em> a type of caption. It’s fine to use
heading-style captions for tables and full-sentence captions for figures
(this is common practice); but you shouldn’t have some figure captions
be heading-style and others be full sentences.</p></li>
<li><p>Each bulleted or numbered list should be consistent within
itself. All elements in the list should either be complete sentences or
sentence fragments; don’t mix-and-match.</p></li>
<li><p>Check for correct and consistent use of punctuation:</p>
<ul>
<li><p>Use quotation marks properly and consistently (single vs. double,
matched opening and closing quotes).</p></li>
<li><p>Be consistent about how quotation marks and other punctuation
interact. I typically use the British style, where the punctuation goes
outside the quotation marks; in computer science, where we often need to
quote code that may include important punctuation, this method is more
precise than the American style, as it ensures that only what is
actually being quoted is inside the quotation marks. For example,
write:</p>
<blockquote>
<p>According to Smith et al., “quotational accuracy is vital”.</p>
</blockquote>
<p>instead of:</p>
<blockquote>
<p>According to Smith et al., “quotational accuracy is vital.”</p>
</blockquote></li>
<li><p>Use the serial (Oxford) comma in comma-separated inline lists.
That is, write “one, two, and three” instead of “one, two and three”. In
almost all cases, it is less ambiguous.</p></li>
<li><p>Use semicolons to separate items in complex inline lists that
have commas within the list items, to reduce ambiguity. It should be
clear what is a list element and what is a subclause of a list
element.</p></li>
<li><p>Use appropriate dashes in appropriate places: a hyphen (-,
<code>-</code>) to separate hyphenated words; an en-dash (–, LaTeX
<code>--</code>) to denote a numerical range (e.g. 7–10); and an em-dash
(—, LaTeX <code>---</code>) as a parenthetical or marker in
text.</p></li>
<li><p>Use em-dashes consistently. It’s fine to have space around an
em-dash (like — this) or not (like—this), but be consistent throughout
the paper.</p></li>
<li><p>Make sure all adjectival phrases are properly hyphenated (e.g. “a
log-based metric”, not “a log based metric”).</p></li>
</ul></li>
</ul>
</section>
<section id="figures" class="level2 checklist">
<h2 class="checklist">Figures</h2>
<ul>
<li><p>All figures should look good (not pixelated) when zoomed way in.
Figures should be at least 300 dpi when rendered to a raster format
(e.g. PNG). This means a 7×5 image should be at least 2100×1500
pixels.</p></li>
<li><p>Text should be legible at a reasonable PDF zoom
resolution.</p></li>
<li><p>Text should be a relatively consistent size between figures,
especially subfigures within the same figure. This is easiest to do by
rendering all images to the same width (relative to their width in the
final paper) in Python or R; 5 inches is often a good width for an image
that will appear in one column in a two-column format with
<code>\includegraphics[width=\textwidth]</code>.</p></li>
<li><p>Figures should not be distorted vertically — their aspect ratio
in the paper should match their source aspect ratio. In LaTeX, never use
<code>height=</code> when including an image; instead, resize it based
only on the width.</p></li>
</ul>
</section>
<section id="references" class="level2 checklist">
<h2 class="checklist">References</h2>
<ul>
<li><p>Make capitalization of paper and conference publication titles
consistent. ACM style is to use title case, but sometimes our BibTeX has
sentence case, depending on where we got it. If you use title case in
your BibTeX, then BibTeX will automatically display it in sentence case
if that is what a style demands; words that should remain capitalized
need to be enclosed in braces (e.g. <code>{LensKit}</code>). It cannot
convert the other direction.</p>
<p>If you use Zotero (which I recommend), it has a further limitation
that it cannot convert from title case to sentence case (because the
braces are a BibTeX thing, not Zotero). The solution for this is:</p>
<ol type="1">
<li>Use the <a href="https://retorque.re/zotero-better-bibtex/">Better
BibTeX plugin</a></li>
<li><a href="https://www.zotero.org/support/kb/sentence_casing">Store
titles in Zotero in <em>sentence case</em></a>, with proper nouns etc.
capitalized</li>
</ol>
<p>Better BibTeX will convert the sentence case to title case in the
BibTeX and insert the protective braces, and your final bibliography
should be correct.</p></li>
<li><p>Be consistent about abbreviated vs. full conference proceeding
names (e.g. “Proceedings of the 43rd ACM SIGIR International Conference
on Advances in Information Retrieval” vs. “Proc. SIGIR ’20”). I
typically use the full title when we don’t have a length limit on
references, but the important thing is that we are consistent throughout
the bibliography. Also applies to journal titles, but the problem is
more common in conference proceedings.</p></li>
<li><p>Be consistent about what fields are included - no one really
cares that the ACM is in New York, and we don’t have that info for all
proceedings, so it can be dropped.</p></li>
<li><p>If we have a DOI, we don’t also need a URL. BibTeX doesn’t
usually include both, but check.</p></li>
<li><p>If we have an ACM / IEEE / Springer publication that doesn’t have
a DOI for some reason, look it up and add it.</p></li>
<li><p>If citing a preprint, check for a current formally-published
version of the work and cite that instead, unless we are specifically
citing the work for something that only appears in the
preprint.</p></li>
<li><p>Sometimes we can get duplicate citations. Check for these and
only cite one.</p></li>
</ul>
<p>When using BibTeX, I recommend making most of these changes in your
reference manager and re-exporting the BibTeX file. That way, the
citations are consistent for the next paper you write. There are two
exceptions: removing URLs (leave the URL in the reference manager, but
remove it from the BibTeX after export) and abbreviating proceedings
titles (I usually find it works better to keep the full title in the
reference manager and edit the BibTeX to have the abbreviated one if we
are using those).</p>
</section>
<section id="content" class="level2 checklist">
<h2 class="checklist">Content</h2>
<ul>
<li><p>Clarify writing on points that reviewers found confusing (either
because they directly commented on the confusion, or because it is a
likely cause of other confusion about the paper reflected in their
comments).</p></li>
<li><p>Make sure we have appropriate acknowledgements. Usually need to
mention:</p>
<ul>
<li>Funding source(s).</li>
<li>Special computing resources (e.g. the compute cluster, with a
citation if appropriate).</li>
<li>People and organizations who provided resources for the paper
(hardware donors, data donors, etc.). We don’t need to mention things we
obtained publicly, such as a publicly-available data set; the citation
is enough for those. But if someone gave us data privately, we need to
acknowledge them.</li>
</ul></li>
<li><p>Ensure author names and affiliations are complete and consistent,
and author names are listed as each author prefers (e.g. I publish as
“Michael D. Ekstrand”).</p></li>
<li><p>If we have promised code, make sure that has been uploaded and an
appropriate link inserted into the paper.</p></li>
</ul>
</section>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Changes]]></title>
        <id>https://md.ekstrandom.net/blog/2023/04/changes</id>
        <link href="https://md.ekstrandom.net/blog/2023/04/changes"/>
        <updated>2023-04-26T04:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="autosize compact right">
<a href="/images/piret-last-meeting.jpg"><img srcset="/images/piret-last-meeting-320.jpg 1x, /images/piret-last-meeting-480.jpg 1.5x, /images/piret-last-meeting-640.jpg 2x" src="/images/piret-last-meeting-640.jpg" alt="A screenshot of the Zoom call of the last PIReT meeting."></a>
</figure>
<p>Things change. Seasons end, and new ones emerge.</p>
<p>In <a href="/blog/2016/12/2016">2016</a>, I joined Boise State
University, and founded the <a href="https://piret.info">People and
Information Research Team</a> with Sole Pera. This was the first (only?)
multi-PI research group in the department, and one of a very small
number of multi-PI recommender systems groups in the US.</p>
<hr class="fold">
<p>In our 7-year run, we’ve graduated (or will soon graduate) about 20
students, published over 100 papers of various kinds, and engaged in
numerous other endeavors. Some were joint, such as co-chairing <a
href="https://recsys.acm.org/recsys18/">RecSys 2018</a>; others were one
or the other of us with the PIReTs serving as a home base, cheerleading
team, and sounding board to refine and improve the ideas, such as the <a
href="https://facctrec.github.io/">FAccTRec</a> and <a
href="https://kidrec.github.io/">KidRec</a> workshop series.</p>
<p>I’m very proud of what we’ve accomplished through the group. I’ve
also grown a lot through these years and through my ongoing discussions
with Sole about how to support our students and how do effective
research. Collaborative mentoring is a fantastic thing — no one really
teaches us how to advise graduate students, so being able to do it in a
context where another professor has a high degree of visibility into
your student’s work and successes and struggles, and you can discuss how
best to support them, is immensely valuable. I’m a significantly better
researcher, scholar, teacher, and adviser because of the time that we’ve
spent leading this little crew together.</p>
<p>But the time has come to bring the PIReT ship to port and take down
the sails. Today was the last regular group meeting. It isn’t completely
done yet, as I have two grad students who will finish under its banner
later this year, and papers in the pipeline that will still be under its
auspices. When Sole moved to TU Delft last summer, we discussed quite a
bit what to do with the group; she stayed involved remotely this year
while we figured out the next steps and the students who started while
she was here (and for some of whom she’s on their committees)
finished.</p>
<p>The conclusion of “what’s next for the group?” is that it’s also time
for me to leave Boise. Jennifer and I are moving to Philadelphia this
summer, where I will be joining the <a
href="https://drexel.edu/cci/academics/information-science-department/">Drexel
University Department of Information Science</a>. I’m really excited
about this opportunity, and the interdisciplinary IS environment will be
a very good context to continue to grow and to develop my scholarship in
the directions I think it needs to go to meaningfully advance the
science of socially-responsible information access.</p>
<figure class="autosize compact left">
<img src="/images/piret-logo.png" alt="The PIReT logo">
</figure>
<p>The PIReT flag is a grouping that best describes what we built at
Boise State, and while we might use it to brand some future
collaborative work neither of us plan to use it as a group name going
forward. It wouldn’t make sense for one thing — she’s in the Web &amp;
Information Science group, and I’m joining an information science
department, so while “people and information research” set our lab apart
in a computer science context, it’s an accurate descriptor for
everything happening in our respective new departments. (I don’t have a
name for my new lab at Drexel yet.)</p>
<p>We spent the group meeting today reflecting on where the group has
been and where we’re each going. It was a good time; there are
definitely <sub>feelings</sub> about the fact that the ship is sailing
its last voyage, but I’m incredibly proud of what we’ve accomplished
with it, and I’m excited for what’s next in my career, in Sole’s, and in
our students’. This is also just the end of a label, not the
collaboration — Sole and I plan to keep working on things together
(hashtag acabesties?), so stay tuned for more research and maybe some
collaborative service ☺️. And you can find Sole’s reflections <a
href="https://twitter.com/DrCh0le/status/1651314549583167490?s=20">on
the birdsite</a>.</p>
<p>In the words of a particular hobbit, “I think I’m quite ready for
another adventure.” And go Phillies!</p>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[2022]]></title>
        <id>https://md.ekstrandom.net/blog/2022/12/in-review</id>
        <link href="https://md.ekstrandom.net/blog/2022/12/in-review"/>
        <updated>2022-12-31T05:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="right compact autosize">
<a href="/images/2022.jpg"><img srcset="/images/2022-320.jpg 1x, /images/2022-480.jpg 1.5x, /images/2022-640.jpg 2x" src="/images/2022-640.jpg" alt="2022"></a>
<figcaption class="credit">
Photo by
<a href="https://unsplash.com/@kellysikkema?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Kelly
Sikkema</a> on
<a href="https://unsplash.com/s/photos/2022?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>
</figcaption>
</figure>
<p>A few years ago, I posted <a href="/blog/tags/year-in-review">annual
reviews</a> of what I did in the year; thought that this year I might
bring that tradition back.</p>
<p>This year has been a year of some major achievements and changes. I
earned tenure, published a major piece of integrative scholarship that
I’ve been working on for a few years, and one of my Ph.D students
successfully defended her proposal.</p>
<p>I’m also working on figuring out what the next phase of structure and
operation looks like for my research lab, since <a
href="https://solepera.github.io">Sole Pera moved to Delft</a>. I’m
thrilled she got this new position! It’s been a very good 6 years
building the PIReTs and having other academic adventures with her, and
we continue to work together remotely on various things. For now, she’s
been continuing to meet with the research group while some of the
current students finish and we figure out what the long-term future of
the PIReT ship will be.</p>
<hr class=fold>
<p>So here’s the list (possibly incomplete):</p>
<ul>
<li><p>Earned tenure!</p></li>
<li><p>Taught our undergraduate ethics class, <a
href="/teaching/cs230-s22.pdf">CS 230</a>, for the first time.</p></li>
<li><p>Taught <a href="https://cs533.ekstrandom.net/f22/">CS 533</a> for
the third time in its current form (my 5th time overall), this time as a
hybrid class with a few in-person synchronous sessions.</p></li>
<li><p>Published our <a href="/pubs/fair-info-access">monograph on
fairness in information access</a> (with Anubrata Das, Robin Burke, and
Fernando Diaz) in <em>Foundations and Trends on Information
Retrieval</em>. This has been a major effort for the last few years, and
it is very satisfying to see it finally in print.</p></li>
<li><p>Served as Program Chair for <a
href="https://recsys.acm.org/recsys22">RecSys 2022</a> with Bracha
Shapira.</p></li>
<li><p>Published a <a href="/pubs/fairness-chapter">chapter in the new
edition of the RecSys Handbook</a> also with Anubrata, Robin, and
Fernando).</p></li>
<li><p>Published an <a href="/pubs/aimag-multisided">article in AI
Magazine</a> with Nasim Sonboli and Robin Burke on the multisided
aspects of fair recommendation.</p></li>
<li><p>Published our work on fair ranking metrics in <a
href="/pubs/fair-ranking">SIGIR 2022</a>, work led by Ph.D student <a
href="https://amifaraj.github.io/">Amifa Raj</a>.</p></li>
<li><p>Published a new study on gender stereotypes in e-commerce search
at <a href="/pubs/fire-dragon">SIGIR eCom 2022</a>, also led and
presented by Amifa.</p></li>
<li><p>Presented a fun short position paper with Sole about <a
href="/pubs/facctrec-matching">the complexity of consumer-side
fairness</a> at the FAccTRec workshop.</p></li>
<li><p>Carried out a new project with undergrad Christine Pinney in
collaboration with <a href="https://www.alex-hanna.com/">Alex Hanna</a>
and Amifa on the use of gender in information retrieval; this has been
<a href="/pubs/chiir-gender">accepted for publication at CHIIR 2023</a>.
Preprint coming in January.</p></li>
<li><p>Published a preprint on <a href="/pubs/recsys-values">values in
recommender systems</a> (led by Jonathan Stray; my primary contribution
was to draft the discussion of fairness and related metrics).</p></li>
<li><p>Unsuccessful paper submissions: SIGIR (1 perspectives paper),
CIKM (1 short paper), CHIIR (2 full papers), FAccT (1 full paper),
RecSys (1 demo), PERSPECTIVES workshop @ RecSys (1 paper), CSCW (1
paper).</p></li>
<li><p>Submitted a position paper to the <em>Transactions on Recommender
Systems</em> special issue on Perspectives on Evaluation.</p></li>
<li><p><a href="https://amifaraj.github.io/">Amifa Raj</a> successfully
defended her Ph.D proposal.</p></li>
<li><p>Srabanti Guha successfully defended her M.S. project
proposal.</p></li>
<li><p>Submitted 3 NSF proposals, including a small corner of an AI
Institute proposal. One declined, two pending.</p></li>
<li><p>Serving as track chair for the new track on <a
href="https://www2023.thewebconf.org/calls/research-tracks/fair-account-trans-ethic/">Fairness,
Accountability, Transparency, and Ethics on the Web</a> for TheWebConf
2023, with Alexandra Olteanu and Krishna Gummadi.</p></li>
<li><p>Co-organized the fourth Fair Ranking track at TREC.</p></li>
<li><p>Gave a <a href="/talks/2022/ibis">keynote on fair info access</a>
at the Information-Based Induction Sciences workshop in Tsukuba,
Japan.</p></li>
<li><p>Gave a <a href="/talks/2022/waseda">seminar talk at Waseda
University</a> in Tokyo, Japan.</p></li>
<li><p>Gave a remote <a href="/talks/2022/umsi">semianr talk for
UMSI</a>.</p></li>
<li><p>Gave an invited talk on <a href="/talks/2022/evalrs">RecSys
fairness evaluation</a> at the EvalRS Analyticup at CIKM 2022.</p></li>
<li><p>Served as a mentor and panelist at the CIKM Ph.D
symposium.</p></li>
<li><p>Served as a mentor at the FAccT Ph.D symposium.</p></li>
<li><p>Continued to serve on the FAccT Executive Committee (now in my
3rd and final year of this term).</p></li>
<li><p>Served as FAccT 2022 Sponsorships Chair with Chelle
Adamson.</p></li>
<li><p>Reviewed several journal papers.</p></li>
<li><p>Continued to serve on department and college undergrad curriculum
committees.</p></li>
<li><p>Participating in a faculty learning community on teaching
portfolios.</p></li>
<li><p>Talked with Reed Albergotti at the <em>Washington Post</em> about
<a
href="https://www.washingtonpost.com/technology/2022/04/16/elon-musk-twitter-algorithm/">why
open-sourcing the algorithm isn’t meaningful</a>.</p></li>
<li><p>Talked with Carolyn Kuimelis at <em>Chronicle of Higher
Education</em> about <a
href="https://www.chronicle.com/newsletter/teaching/2022-12-01?cid2=gen_login_refresh&amp;cid=gen_sign_in">late
work</a> (also described my policy in detail in <a
href="/blog/2022/08/late-work">this blog post</a>).</p></li>
<li><p>Refreshed my web site &amp; presentation slide design. The slide
design is all–new — see my recent talks for examples.</p></li>
</ul>
<section id="teaching" class="level2">
<h2>Teaching</h2>
<ul>
<li>Spring — CS 230 (Ethics)</li>
<li>Fall — CS 533 (Intro to Data Science)</li>
</ul>
</section>
<section id="active-grants" class="level2">
<h2>Active Grants</h2>
<ul>
<li><a href="/research/career">NSF CAREER</a></li>
</ul>
</section>
<section id="publications" class="level2">
<h2>Publications</h2>
<div class="citelist">
<p class="citation conference" data-label="CHIIR23" data-year="2023" data-date="2023-03-19" data-pub-type="conference" data-pub-subtype="full" data-doi="10.1145/3576840.3578316" data-google-citations="39" data-semantic-citations="15">
<span class="author vcard fn student undergrad presenter">Christine
Pinney</span>, <span class="author vcard fn advisee">Amifa Raj</span>,
<span class="author vcard fn">Alex Hanna</span>, and <span
class="author vcard fn me">Michael D. Ekstrand</span>. <span
class="year">2023</span>.
<a href="/pubs/chiir-gender" class="title">Much Ado About Gender:
Current Practices and Future Recommendations for Appropriate
Gender-Aware Information Access</a>. In
<cite class="pub-title">Proceedings of the 2023 Conference on Human
Information Interaction and Retrieval</cite> (<span
class="pub-abbr"><abbr class="acro">CHIIR</abbr> ’23</span>), Mar 19,
2023. pp. 269–279. <span class="linkid"
data-link="doi"><abbr class="idtype doi" title="Digital Object Identifier">DOI</abbr>
<a class="id doi" href="https://dl.acm.org/doi/10.1145/3576840.3578316?cid=81444608287">10.1145/3576840.3578316</a>.
</span><span class="linkid"
data-link="arxiv">arXiv:<a class="id.arxiv" href="https://arxiv.org/abs/2301.04780">2301.04780</a>.
</span><span class="linkid"
data-link="nsf"><abbr class="idtype nsf acro" title="National Science Foundation">NSF</abbr>
<abbr class="idtype.par.acro" title="Public Access Repository">PAR</abbr>
<a class="id par" href="https://par.nsf.gov/biblio/10423693">10423693</a>.
</span><span class="pub-stats">Acceptance rate: 39.4%.</span> <span
class="cite-stats google"><a class="rev-citations" href="https://scholar.google.com/scholar?cites=12778469865597211195,3962674036878103520" title="Reverse citations">Cited
39 times</a>.</span> <span
class="cite-stats semantic"><a class="rev-citations" href="https://api.semanticscholar.org/88327d995d367f1d38d8d88e1ebfcb8c85a2269d#citing-papers" title="Reverse citations">Cited
15 times</a>.</span>
</p>
<p class="citation preprint" data-label="FAccTRec22" data-year="2022" data-date="2022-09-23" data-pub-type="preprint" data-pub-subtype="workshop" data-google-citations="10" data-semantic-citations="3">
<span class="author vcard fn me presenter">Michael D. Ekstrand</span>
and <span class="author vcard fn">Maria Soledad Pera</span>. <span
class="year">2022</span>.
<a href="/pubs/facctrec-matching" class="title">Matching Consumer
Fairness Objectives &amp; Strategies for RecSys</a>. Presented at the
<span class="venue">5th FAccTrec Workshop on Responsible
Recommendation</span> at RecSys 2022 (peer-reviewed but not archived).
<span class="linkid"
data-link="arxiv">arXiv:<a class="id.arxiv" href="https://arxiv.org/abs/2209.02662">2209.02662</a>
[cs.IR]. </span><span
class="cite-stats google"><a class="rev-citations" href="https://scholar.google.com/scholar?cites=16549132596688661361" title="Reverse citations">Cited
10 times</a>.</span> <span
class="cite-stats semantic"><a class="rev-citations" href="https://api.semanticscholar.org/506853223c282f3bf7507aa92f0e9404a8b503e6#citing-papers" title="Reverse citations">Cited
3 times</a>.</span>
</p>
<p class="citation journal" data-label="TORS24v" data-year="2024" data-date="2024-06-05" data-pub-type="journal" data-pub-subtype="article" data-doi="10.1145/3632297" data-google-citations="200" data-semantic-citations="74">
<span class="author vcard fn">Jonathan Stray</span>, <span
class="author vcard fn">Alon Halevy</span>, <span
class="author vcard fn">Parisa Assar</span>, <span
class="author vcard fn">Dylan Hadfield-Menell</span>, <span
class="author vcard fn">Chloe Bakalar</span>, <span
class="author vcard fn">Craig Boutilier</span>, <span
class="author vcard fn">Amar Ashar</span>, <span
class="author vcard fn">Lex Beattie</span>, <span
class="author vcard fn me">Michael Ekstrand</span>, <span
class="author vcard fn">Claire Leibowicz</span>, <span
class="author vcard fn">Connie Moon Sehat</span>, <span
class="author vcard fn">Sara Johansen</span>, <span
class="author vcard fn">Lianne Kerlin</span>, <span
class="author vcard fn">David Vickrey</span>, <span
class="author vcard fn">Spandana Singh</span>, <span
class="author vcard fn">Sanne Vrijenhoek</span>, <span
class="author vcard fn">Amy Zhang</span>, <span
class="author vcard fn">McKane Andrus</span>, <span
class="author vcard fn">Natali Helberger</span>, <span
class="author vcard fn">Polina Proutskova</span>, <span
class="author vcard fn">Tanushree Mitra</span>, and <span
class="author vcard fn">Nina Vasan</span>. <span
class="year">2024</span>.
<a href="/pubs/recsys-values" class="title">Building Human Values into
Recommender Systems: An Interdisciplinary Synthesis</a>.
<cite class="pub-title">Transactions on Recommender Systems</cite> <span
class="volume">2</span>(<span class="number">3</span>) (June 2024;
online Nov 12, 2023), 20:1–57. <span class="linkid"
data-link="doi"><abbr class="idtype doi" title="Digital Object Identifier">DOI</abbr>
<a class="id doi" href="https://dl.acm.org/doi/10.1145/3632297?cid=81444608287">10.1145/3632297</a>.
</span><span class="linkid"
data-link="arxiv">arXiv:<a class="id.arxiv" href="https://arxiv.org/abs/2207.10192">2207.10192</a>
[cs.IR]. </span><span
class="cite-stats google"><a class="rev-citations" href="https://scholar.google.com/scholar?cites=13622633230483594875,12305227976644286478" title="Reverse citations">Cited
200 times</a>.</span> <span
class="cite-stats semantic"><a class="rev-citations" href="https://api.semanticscholar.org/88a4172a207beb4275914d35c6a19c9de46091de#citing-papers" title="Reverse citations">Cited
74 times</a>.</span>
</p>
<p class="citation conference" data-label="SIGIRec22" data-year="2022" data-date="2022-07-15" data-pub-type="conference" data-pub-subtype="workshop" data-doi="10.48550/arXiv.2206.13747" data-google-citations="15" data-semantic-citations="6">
<span class="author vcard fn advisee presenter">Amifa Raj</span> and
<span class="author vcard fn me">Michael D. Ekstrand</span>. <span
class="year">2022</span>. <a href="/pubs/fire-dragon" class="title">Fire
Dragon and Unicorn Princess: Gender Stereotypes and Children’s Products
in Search Engine Responses</a>. In <cite class="pub-title">SIGIR eCom
’22</cite>, Jul 15, 2022. 9 pp.  <span class="linkid"
data-link="doi"><abbr class="idtype doi" title="Digital Object Identifier">DOI</abbr>
<a class="id doi" href="https://doi.org/10.48550/arXiv.2206.13747">10.48550/arXiv.2206.13747</a>.
</span><span class="linkid"
data-link="arxiv">arXiv:<a class="id.arxiv" href="https://arxiv.org/abs/2206.13747">2206.13747</a>
[cs.IR]. </span><span
class="cite-stats google"><a class="rev-citations" href="https://scholar.google.com/scholar?cites=4192301315304744108,4834268681421282249" title="Reverse citations">Cited
15 times</a>.</span> <span
class="cite-stats semantic"><a class="rev-citations" href="https://api.semanticscholar.org/9df05d5e46aa1027857c51a5cc4799b78c9b0f78#citing-papers" title="Reverse citations">Cited
6 times</a>.</span>
</p>
<p class="citation journal" data-label="FnT22" data-year="2022" data-date="2022-07-11" data-pub-type="journal" data-pub-subtype="article" data-doi="10.1561/1500000079" data-google-citations="299" data-semantic-citations="108">
<span class="author vcard fn me">Michael D. Ekstrand</span>, <span
class="author vcard fn">Anubrata Das</span>, <span
class="author vcard fn">Robin Burke</span>, and <span
class="author vcard fn">Fernando Diaz</span>. <span
class="year">2022</span>.
<a href="/pubs/fair-info-access" class="title">Fairness in Information
Access Systems</a>. <cite class="pub-title">Foundations and Trends® in
Information Retrieval</cite> <span class="volume">16</span>(<span
class="number">1–2</span>) (July 2022), 1–177. <span class="linkid"
data-link="doi"><abbr class="idtype doi" title="Digital Object Identifier">DOI</abbr>
<a class="id doi" href="https://doi.org/10.1561/1500000079">10.1561/1500000079</a>.
</span><span class="linkid"
data-link="arxiv">arXiv:<a class="id.arxiv" href="https://arxiv.org/abs/2105.05779">2105.05779</a>
[cs.IR]. </span><span class="linkid"
data-link="nsf"><abbr class="idtype nsf acro" title="National Science Foundation">NSF</abbr>
<abbr class="idtype.par.acro" title="Public Access Repository">PAR</abbr>
<a class="id par" href="https://par.nsf.gov/biblio/10347630">10347630</a>.
</span><span class="pub-stats">Impact factor: 8.</span> <span
class="cite-stats google"><a class="rev-citations" href="https://scholar.google.com/scholar?cites=13992970703424482383,15486826628851819116,6079743450968609191" title="Reverse citations">Cited
299 times</a>.</span> <span
class="cite-stats semantic"><a class="rev-citations" href="https://api.semanticscholar.org/82b1322fa52bc60cadb32f7c88f3af050c445276#citing-papers" title="Reverse citations">Cited
108 times</a>.</span>
</p>
<p class="citation conference" data-label="SIGIR22" data-year="2022" data-date="2022-07-11" data-pub-type="conference" data-pub-subtype="full" data-doi="10.1145/3477495.3532018" data-google-citations="98" data-semantic-citations="58">
<span class="author vcard fn advisee presenter">Amifa Raj</span> and
<span class="author vcard fn me">Michael D. Ekstrand</span>. <span
class="year">2022</span>.
<a href="/pubs/fair-ranking" class="title">Measuring Fairness in Ranked
Results: An Analytical and Empirical Comparison</a>. In
<cite class="pub-title">Proceedings of the 45th International ACM SIGIR
Conference on Research and Development in Information Retrieval</cite>
(<span class="pub-abbr"><abbr class="acro">SIGIR</abbr> ’22</span>), Jul
11, 2022. pp. 726–736. <span class="linkid"
data-link="doi"><abbr class="idtype doi" title="Digital Object Identifier">DOI</abbr>
<a class="id doi" href="https://dl.acm.org/doi/10.1145/3477495.3532018?cid=81444608287">10.1145/3477495.3532018</a>.
</span><span class="linkid"
data-link="nsf"><abbr class="idtype nsf acro" title="National Science Foundation">NSF</abbr>
<abbr class="idtype.par.acro" title="Public Access Repository">PAR</abbr>
<a class="id par" href="https://par.nsf.gov/biblio/10329880">10329880</a>.
</span><span class="pub-stats">Acceptance rate: 20%.</span> <span
class="cite-stats google"><a class="rev-citations" href="https://scholar.google.com/scholar?cites=13645699337591415651,5296490850648056138" title="Reverse citations">Cited
98 times</a>.</span> <span
class="cite-stats semantic"><a class="rev-citations" href="https://api.semanticscholar.org/9e9ae9bfc1d4e651ee6f57a02284c67c4efd6bf6#citing-papers" title="Reverse citations">Cited
58 times</a>.</span>
</p>
<p class="citation magazine" data-label="AIMAG22" data-year="2022" data-date="2022-06-23" data-pub-type="magazine" data-doi="10.1002/aaai.12054" data-google-citations="57" data-semantic-citations="25">
<span class="author vcard fn">Nasim Sonboli</span>, <span
class="author vcard fn">Robin Burke</span>, <span
class="author vcard fn me">Michael Ekstrand</span>, and <span
class="author vcard fn">Rishabh Mehrotra</span>. <span
class="year">2022</span>.
<a href="/pubs/aimag-multisided" class="title">The Multisided Complexity
of Fairness in Recommender Systems</a>. <cite class="pub-title">AI
Magazine</cite> <span class="volume">43</span>(<span
class="number">2</span>) (June 2022), 164–176. <span class="linkid"
data-link="doi"><abbr class="idtype doi" title="Digital Object Identifier">DOI</abbr>
<a class="id doi" href="https://doi.org/10.1002/aaai.12054">10.1002/aaai.12054</a>.
</span><span class="linkid"
data-link="nsf"><abbr class="idtype nsf acro" title="National Science Foundation">NSF</abbr>
<abbr class="idtype.par.acro" title="Public Access Repository">PAR</abbr>
<a class="id par" href="https://par.nsf.gov/biblio/10334796">10334796</a>.
</span><span
class="cite-stats google"><a class="rev-citations" href="https://scholar.google.com/scholar?cites=16137149255417014903" title="Reverse citations">Cited
57 times</a>.</span> <span
class="cite-stats semantic"><a class="rev-citations" href="https://api.semanticscholar.org/f374d46a50f01c8e589d8d29dd8bd6f72a85688e#citing-papers" title="Reverse citations">Cited
25 times</a>.</span>
</p>
<p class="citation chapter" data-label="RSHB3E" data-year="2022" data-date="2022-04-26" data-pub-type="chapter" data-doi="10.1007/978-1-0716-2197-4_18" data-google-citations="73" data-semantic-citations="22">
<span class="author vcard fn me">Michael D. Ekstrand</span>, <span
class="author vcard fn">Anubrata Das</span>, <span
class="author vcard fn">Robin Burke</span>, and <span
class="author vcard fn">Fernando Diaz</span>. <span
class="year">2022</span>.
<a href="/pubs/fairness-chapter" class="title">Fairness in Recommender
Systems</a>. In <cite class="pub-title">Recommender Systems
Handbook</cite> (3rd edition). <span class="editor vcard fn">Francesco
Ricci</span>, <span class="editor vcard fn">Lior Roach</span>, and <span
class="editor vcard fn">Bracha Shapira</span>, eds. <span
class="publisher">Springer-Verlag</span>pp. 679–707. <span
class="linkid"
data-link="doi"><abbr class="idtype doi" title="Digital Object Identifier">DOI</abbr>
<a class="id doi" href="https://doi.org/10.1007/978-1-0716-2197-4_18">10.1007/978-1-0716-2197-4_18</a>.
</span><span class="linkid"
data-link="isbn"><abbr class="idtype isbn acro" title="International Standard Book Number">ISBN</abbr>
<a class="id isbn" href="https://www.worldcat.org/search?q=isbn:9781071621967">978-1-0716-2196-7</a>.
</span><span
class="cite-stats google"><a class="rev-citations" href="https://scholar.google.com/scholar?cites=14544544406367188358" title="Reverse citations">Cited
73 times</a>.</span> <span
class="cite-stats semantic"><a class="rev-citations" href="https://api.semanticscholar.org/977bc1d5f9f1ebadc5cbffb3a3dc5f48c292a0aa#citing-papers" title="Reverse citations">Cited
22 times</a>.</span>
</p>
<p class="citation conference" data-label="⸘2022‽" data-year="2022" data-date="2022-03-01" data-pub-type="conference" data-pub-subtype="preface">
<span class="author vcard fn me">Michael D. Ekstrand</span>, <span
class="author vcard fn">Graham McDonald</span>, <span
class="author vcard fn advisee">Amifa Raj</span>, and <span
class="author vcard fn">Isaac Johnson</span>. <span
class="year">2022</span>.
<a href="/pubs/fair-trec-2021" class="title">Overview of the TREC 2021
Fair Ranking Track</a>. Meeting summary in <cite class="pub-title">The
Thirtieth Text REtrieval Conference (TREC 2021) Proceedings</cite>
(<span class="pub-abbr"><abbr class="acro">TREC</abbr> 2021</span>), Mar
1, 2022. <span
class="linkid"><a class="id url" href="https://trec.nist.gov/pubs/trec30/papers/Overview-F.pdf">trec.nist.gov/pubs/trec30/papers/Overview-F.pdf</a>.
</span>
</p>
</div>
</section>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[2022 State of the Tools]]></title>
        <id>https://md.ekstrandom.net/blog/2022/12/tools</id>
        <link href="https://md.ekstrandom.net/blog/2022/12/tools"/>
        <updated>2022-12-27T05:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="right compact autosize">
<a href="/images/many-tools.jpg"><img srcset="/images/many-tools-320.jpg 1x, /images/many-tools-480.jpg 1.5x, /images/many-tools-640.jpg 2x" src="/images/many-tools-640.jpg" alt="A picture of many tools on a white surface."></a>
<figcaption class="credit">
Photo by
<a href="https://unsplash.com/@carlevarino?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Cesar
Carlevarino Aragon</a> on
<a href="https://unsplash.com/s/photos/tools?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>
</figcaption>
</figure>
<p>It’s time for this year’s <a href="/blog/tags/tools">State of the
Tools</a>. Things are a bit in flux this year, as I’m working on
transitioning my endpoints from Windows to macOS, but I’ll run down
where I’m at and where I’m headed; there have also been substantial
changes in our personal computing infrastructure.</p>
<section id="hardware-os" class="level2">
<h2>Hardware &amp; OS</h2>
<p>Organizing these posts can be a bit random, so let’s start with
systems themselves.</p>
<section id="work-endpoint-computing" class="level3">
<h3>Work Endpoint Computing</h3>
<p>I’ve moved entirely into my Surface Laptop 4 (i7, 32GB/1TB) running
Windows 11 for work computing now, and reloaded my i9 to be a Linux
compute server that lives on my desk. Since I work from home 1–2 days
most weeks this is a lot more convenient than having different sets of
windows and browser tabs at work and home. It also addresses the problem
that Zoom refuses to allow me to log in to more than one computer at a
time — I can mostly just be logged in on the laptop.</p>
<p>At the office, this connects to a Surface Dock 2 driving 2 4K Dell
monitors, and the laptop sits to the side to give me a 3rd screen. I use
a Surface bluetooth ergo keyboard and the wireless version of the <a
href="https://www.kensington.com/p/products/ergonomic-desk-accessories/ergonomic-input-devices/expert-mouse-wireless-trackball-1/">Kensington
Expert Mouse</a>. I’ve used some version or another of the Expert Mouse
since about 2010, and it’s a solid product that helps a lot with my RSI.
The product has been on the market for probably 20 years at this point,
and if they ever stop production it will be very sad.</p>
<p>I also have an M1 Mac Mini for software testing and driving my shared
monitor, and an old MacBook Air for debugging on Intel-based Macs. I
also still have the Surface Go 2, which I mostly use as a tablet. In my
next work computer refresh, I plan to get a 14” MacBook Pro, and keep a
Windows VM on a server somewhere for Windows-based testing and
debugging. While I appreciated the travel weight benefits of a 10”
tablet for my portable computing, life and the world change, and it’s
really more convenient to have a usable keyboard and screen that are
still portable.</p>
</section>
<section id="work-backend-computing" class="level3">
<h3>Work Backend Computing</h3>
<p>I’ve moved more computing to remotely-accessed iron since switching
to a purely laptop endpoint. My Dell workstation is now a compute server
(8-core i9, 64GB RAM, 512GB SDD + 10TB HDD), and I put a new 8GB
Turing-based Quadro card in it (pretty modest, but the largest card this
computer’s case and PSU can handle). Note to self, don’t by a small form
factor box for heavy compute. This machine is only used by me, not the
lab.</p>
<p>My research group has a dual Xeon server with 24 total cores for data
storage and modest compute. The CPUs are ancient and only clocked at
2GHz, so it isn’t very fast, but it has a lot of memory &amp; a decent
amount of disk, so it’s just fine for storage and interactive computing.
Large compute-intensive jobs we run on the university’s clusters. I run
medium jobs on my dedicated server.</p>
<p>Both of these machines are running RHEL8 with XFS filesystems; DVC
can use the reflink feature in modern XFS to reduce storage requirements
quite a bit.</p>
</section>
<section id="personal-endpoint" class="level3">
<h3>Personal Endpoint</h3>
<p>We’re diving a bit deeper into the Apple ecosystem this year, and
bought an M2 MacBook Air to replace my aging (and showing its age)
Surface Pro 4. I used a Mac for work at Texas State 2014–2016 and didn’t
really like it, but working with this thing I am pretty much sold. The
battery lasts for ages, it’s incredibly fast, and having a consistent
CLI experience between local and remote systems is nice. The hardware is
also very nice. I can even do a bit of light gaming on it.</p>
<p>We also still use a Windows desktop for most of our gaming some
personal computing work. I’m thinking hard about getting a Steam Deck
and moving most of my gaming over to that in the next year or two, but
we will see. Not planning to make that purchase in the next few months.
Overall, I think the Deck (or the Deck 2 that I assume is coming) would
be a pretty good fit for my particular gaming needs and style,
especially driving the TV with a dock and my XBox controller.</p>
</section>
<section id="personal-infrastructure" class="level3">
<h3>Personal Infrastructure</h3>
<p>Our QNAP TS-269L NAS is still chugging along, and in addition to
storing backups of teaching materials, it’s doing extra duty as our
router and firewall and boasting an upgrade to 8TB of mirrored storage.
Its CPU is pretty meager; an Atom D2701 that, while 64-bit, doesn’t have
crypto extensions or even frequency scaling. For the time being, though,
it is getting the job done.</p>
<p>It took over router duty when our NetGear router decided for whatever
reason that software updates are optional. Even after I manually
installed the latest firmware, it refused to detect and install
subsequent updates. Security updates are pretty important for the home
network perimeter, so I decided to move routing &amp; firewall duties to
a machine whose OS I could control directly and make sure was up to date
and use the NetGear in AP-only mode. Since the NAS has two network
ports, it’s filling that role for now.</p>
<p>The NAS started the year running Alpine with XFS on Linux software
raid, but I tried out FreeBSD on it again with ZFS. This was a pretty
ergonomic environment, and <code>pf</code> is a very approachable
firewall language when it came time make it a router
(<code>iptables</code> has usually confused me), but there were a couple
big issues:</p>
<ul>
<li>Significant performance problems when doing CPU- and disk-intensive
work, such as synchronizing large file directories. I do not know the
cause, but my suspicion is that its task scheduler is not as effective
at handling significant load on underpowered hardware; filesystem and
the transfer application would get priority over network routing, so our
machines would fall off the network.</li>
<li>FreeBSD’s security reputation is… spotty. From what I’ve read in
various sources trying to get a good picture is basically that there are
some good ideas, but without near the attention that Linux has had,
there are likely many outstanding security-relevant bugs and things
don’t get caught as quickly.</li>
</ul>
<p>So back to Alpine Linux but keeping ZFS (which Alpine supports very
well). After a few false starts, I got the necessary firewalling rules
working in <code>nftables</code> (which is a substantial ergonomic
improvement from iptables IMO). Alpine also has a pretty BSD flavor to
its administration, due to using OpenRC and lots of classic shell
scripts. I like the niceties of modern Linux for a lot of things, but
for my basic network infrastructure at home Alpine is working quite
well, and I haven’t seen a recurrence of the performance problems I had
on FreeBSD. And with the <code>gcompat</code> package, VS Code remote
editing even works. I’ve also installed Wireguard for personal device
VPNs, so I can browse (more) securely while traveling.</p>
<p>The Raspberry Pi 0 media widget isn’t seeing a lot of use these days,
but it’s still there, and still running Alpine fine, with shairport-sync
attempting to provide Apple streaming support but glitching out rather
more frequently than I would like.</p>
<p>In the next year or two, I’m hoping to add a dedicated router (NanoPi
R5C is the current leader), replace the NetGear with a WiFi 6 access
point, and upgrade the NAS to something with a more modern CPU.</p>
</section>
<section id="mobile" class="level3">
<h3>Mobile</h3>
<p>Not much to say here. Still on iPhone &amp; Apple Watch, no plans to
change that.</p>
</section>
</section>
<section id="interactive-software" class="level2">
<h2>Interactive Software</h2>
<p>Across my various devices, I tend to rely on the same software for my
interactive (graphical) computing:</p>
<ul>
<li>Chrome for work browsing, Firefox for personal.</li>
<li>Office for editing, spreadsheets, and presentations; I do use Google
Docs for work stuff that is lightweight and/or needs high
collaboration.</li>
<li>VS Code for text editing.</li>
<li>Overleaf for LaTeX document preparation.</li>
<li>Paperpile for reference management.</li>
<li>DynaList for tracking work notes and my <a
href="/blog/2021/02/runway">runway document</a>.</li>
<li>FreeCommander for Windows file management, Marta on macOS (when I am
not just using the system’s native file browser).</li>
<li>The Affinity suite for graphics &amp; photo editing.</li>
<li>Visio for diagramming (when PowerPoint or diagrams.net won’t do).
I’m not using Grapholite much any more. After switching to Mac I will
look at OmniGraffle.</li>
<li>Drawboard PDF for tablet-based PDF reading and annotation; Acrobat
Pro on laptop/desktop.</li>
<li>1Password for password management.</li>
<li>iTerm 2 for my macOS terminal (Microsoft Terminal on Windows).</li>
</ul>
</section>
<section id="command-line" class="level2">
<h2>Command Line</h2>
<p>I do a fair amount of work from the command line, both for running
the software and analyses I’m doing and also for general file and data
manipulations. Key pieces of this load:</p>
<ul>
<li><code>zsh</code> on *nix (including WSL), PowerShell on
Windows.</li>
<li><code>nano</code> for quick terminal editing; I’ve also been
experimenting with Helix and Micro but the <code>nano</code> keybindings
are pretty engrained now.</li>
<li><code>tmux</code> for terminal multiplexing / disconnection.</li>
<li><code>mosh</code> for connecting to servers at home, will probably
start using it more across the board. I’m now just using OpenSSH’s
direct PKCS#11 support for my YubiKey.</li>
<li><code>direnv</code> to provide ergonomic project-specific
configuration.</li>
<li><code>zoxide</code> (on *nix) and <code>ZLocation</code>
(PowerShell) for directory navigation.</li>
<li><code>htop</code> for performance monitoring.</li>
<li><code>bat</code> for file viewing.</li>
<li><code>fd</code> for finding files.</li>
<li><code>ripgrep</code> for searching files.</li>
<li><code>ndcu</code> and <code>dust</code> for managing disk
usage.</li>
<li><code>exa</code> for listing files.</li>
<li><code>pandoc</code> for Markdown processing.</li>
</ul>
</section>
<section id="data-infrastructure" class="level2">
<h2>Data Infrastructure</h2>
<p>On the personal side, we moved off of Microsoft for our primary data
infrastructure. E-mail is now on Fastmail and we’re using iCloud for
file sharing, calendars, contacts, tasks, etc. Microsoft was working
fine for us, but with moving towards macOS it wasn’t making as much
sense, and we were able to cut our infrastructure &amp; services bill a
bit. Using Syncthing for data transfer between endpoints and the NAS,
Samba/CIFS for direct access to NAS storage, and Backblaze for endpoint
backups.</p>
<p>For work, there are a few things in play:</p>
<ul>
<li>Google Drive for cloud/synchronized document storage (and all the
Google Doc storage, of course).</li>
<li><code>git</code> and GitHub for source code and text document
management. Most of my documents either live in Google Drive or a Git
repo.</li>
<li><a href="https://dvc.org">Data Version Control</a>
(<code>dvc</code>) for managing research data and pipelines.</li>
<li>Minio (S3-compatible storage server) for storing and synchronizing
DVC-managed data.</li>
<li><code>rsync</code> and <code>robocopy</code> for CLI data transfer;
WinSCP for GUI transfer.</li>
<li><a href="https://kopia.io/">Kopia</a> for endpoint backup, using a
university CIFS drive as the backup target.</li>
</ul>
</section>
<section id="programming-and-science" class="level2">
<h2>Programming and Science</h2>
<p>I am still using Python + Pandas + NumPy as my primary software
development environment for scientific computing &amp; open-source work.
I wouldn’t say I love it, but it’s widely-known, gets the job done, and
has libraries for a lot of things. I’ve been working on filling a few
gaps myself, such as the <a
href="https://seedbank.lenskit.org"><code>seedbank</code></a> package
for random number generator initialization. For plotting I’m mostly
using <code>plotnine</code>, but sometimes <code>seaborn</code> and
<code>matplotlib</code>.</p>
<p>I use Rust for high-performance data processing code; this year, I
finished rewriting the <a href="https://bookdata.piret.info">Book Data
Tools</a> to be completely in Rust (except for notebooks computing
statistics on the data set) instead of a combination of Rust, Python,
and SQL.</p>
<p>JavaScript is my primary language for web stuff; this web site is
generated with a <a href="/colophon">custom JavaScript codebase</a>.
I’ve been experimenting with Deno some as an alternative to node.js, and
might start doing more with it. I also reach for JavaScript when doing
some data processing that is heavily web-native, like extracting data
from a Twitter archive dump.</p>
<p>I’ve also started playing around a bit more with TCL for lightweight
scripting. I’ve used it for a while for helping with some setup bits for
my Unix shell environment, and have now written some admin scripts in
it. It definitely has some warts and missing holes, but it’s a rather
pleasant language to do simple things in, and it’s fantastic for
building eDSLs.</p>
<p>I use Conda/Mamba (typically through the Mambaforge distribution
these days) for managing my primary scientific software environments, as
it allows me to get Python, Rust, R, and other tools as needed. These
days, if something isn’t in <code>conda-forge</code> or available on a
pretty basic OS install, I don’t depend on it in my research projects,
so that students and collaborators can get all the requirements from
<code>environment.yml</code>.</p>
<p>For projects that aren’t in Conda, I use <code>rustup</code> for
managing my Rust versions and <code>asdf</code> for managing most other
interpreter environments (Python, Node, Ruby, etc.). I’ve been using
<code>pip-tools</code> to pre-resolve Python dependencies for lack of a
better solution.</p>
</section>
<section id="media-production" class="level2">
<h2>Media Production</h2>
<p>I’m not recording as many videos as I did the year I did the
build-out of <a href="https://cs533.ekstrandom.net">CS 533</a>, but I do
still record and edit a fair number.</p>
<p>Camtasia is my go-to recording &amp; video production software; it’s
much easier to use for my use case (educational videos) than something
like Preimier. I also have a USB shuttle to help with precise navigation
while editing.</p>
<p>The game-changing addition this year was TechSmith Audiate. It’s an
audio editor and transcriber that allows you to edit an audio track by
editing its text transcription. Corrections just correct the transcript
without editing the underlying audio; deletions edit the audio track as
well. This way I can more aggressively trim my videos to delete blank
spaces, false starts, etc.; previously, I only edited out big gaps,
because more detailed editing to clean up the speech is very
labor-intensive. It integrates with Camtasia, so I can export the audio
track from Camtasia to Audiate, clean it up, and round-trip back with an
edit list that Camtasia uses to trim the corresponding video. This
cleanup process also gives me working captions at the same time. I
haven’t gone back and re-edited all my videos with this, but it makes it
much less time-taking to produce both a cleaner video and edited
captions for new work.</p>
<p>For recording hardware, I’m using a Logitech Brio as my primary
webcam, and a Blue Yeticaster as the microphone. I have a green screen
that can clip to the back of my chair and some USB lights to facilitate
chroma-key work. I also use my phone sometimes, with the <a
href="https://reincubate.com/camo/">Camo</a> software that turns a phone
into a webcam. This, combined with a desk-mounted boom arm and a
QuadLock mount, allows me to do document camera work in my office either
on video or on Zoom without needing to record and edit in a separate
video stream. I also use my phone for mobile video recording.</p>
<p>I’m still using Unsplash and The Noun Project for sourcing most of my
artwork &amp; icons, with the occasional addition from OpenClipArt.</p>
</section>
<section id="analog-information" class="level2">
<h2>Analog Information</h2>
<p>I’m still using Leuchtturm1917 A5 notebooks and a collection of
fountain pens for my daily and weekly <a
href="/blog/series/productivity">productivity management</a>,
note-taking, and journaling. I use a Pentel Orenz mechanical pencil for
working out maths, sketching diagrams, and other things where I need to
be able to erase.</p>
<p>I also bought a turntable this year and began collecting vinyl music.
Tactile, analog engagement helps me ground myself and feel more
connected to what I’m doing; it’s been effective in my journaling, and I
wanted to bring that to a more purposeful connection to music (in some
contexts — I still make heavy use of streaming as well). I’ve been
putting together an eclectic collection including P!nk, Sturgill
Simpson, The New York Rock &amp; Roll Orchestra, Chicago Transit
Authority (before they changed their name — found that album for $1 in
the bargain bin), Carly Rae Jepsen, and many others. My <a
href="/blog/2021/02/ritual">weekly ritual</a> has now extended to
include spinning an album to go with my whiskey and longhand
planning.</p>
</section>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Upcoming Talks in Japan]]></title>
        <id>https://md.ekstrandom.net/blog/2022/10/japan</id>
        <link href="https://md.ekstrandom.net/blog/2022/10/japan"/>
        <updated>2022-10-05T04:00:00.000Z</updated>
        <content type="html"><![CDATA[<p>I’m starting to do some speaking again, and now that Japan has opened
back up for travel, I’m very pleased to be giving a couple of talks
later this month:</p>
<ul>
<li>On November 19, I will be speaking at <a
href="http://sakailab.com/ekstrand20221119/">Waseda University</a>,
hosted by Tetsuya Sakai.</li>
<li>On November 20, I will be giving a <a
href="https://ibisml.org/ibis2022/invited/">keynote talk</a> at IBIS
2022 (Information-Based Induction Sciences). Thank you very much to
Toshihiro Kamishima for inviting me!</li>
</ul>
<p>I’ll have a little time around the edges of my trip, so if you are in
Tokyo and would like to connect, send me an e-mail and let’s see what we
can arrange. Or say hi at IBIS!</p>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Late Work]]></title>
        <id>https://md.ekstrandom.net/blog/2022/08/late-work</id>
        <link href="https://md.ekstrandom.net/blog/2022/08/late-work"/>
        <updated>2022-08-13T04:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="compact autosize right">
<a href="/images/white-rabbit.png"><img srcset="/images/white-rabbit-320.png 1x, /images/white-rabbit-480.png 1.5x, /images/white-rabbit-640.png 2x" src="/images/white-rabbit-640.png" alt="A white rabbit wearing a waistcoat and looking at his pocket-watch, running late. From Lewis Carrol's Alice in Wonderland." class="webfeedsFeaturedVisual"></a>
<figcaption class="credit">
White Rabbit from <cite>The Nursery Alice<cite>, illustrated by John
Tenniel.
</figcaption>
</figure>
<p>As long as I have been teaching, I’ve used a “late day” policy in
most of my classes. I designed this policy after learning about
universal course design while taking <em>Preparing Future Faculty</em>
at the University of Minnesota, but I don’t think I’ve ever publicly
written down the motivations and design of this policy. So here you
go!</p>
<section id="why-keep-deadlines" class="level2">
<h2>Why Keep Deadlines?</h2>
<p>I generally cannot do away with deadlines in my classes. Deadlines
allow me (or the grader) to grade in a predictable timeframe, with all
the assignments in; it’s easier to maintain consistency when grading all
submissions in a single effort. They also provide a cutoff for working
on the assignment so I can share my solution with the class. For some
classes with assignments that produce more varied student responses,
this wouldn’t matter near as much, but most of my classes are
programming or data analysis classes where everyone is working on the
same problem for most of the assignments.</p>
<p>I did try a deadline-free approach one year for my graduate
recommender systems course — all material was due at the end of the term
— but the effect was that almost all students didn’t start working on
the assignments until the last week or two of class, and had significant
difficulty completing them in time. Students benefit from deadlines to
structure their own work as well.</p>
<p>For all of these reasons, it wouldn’t work to do away with deadlines
entirely in my teaching. I also don’t think it would work to just have
deadlines with unlimited free extensions — it might guide some students
to submission, but makes the deadlines relatively meaningless.</p>
<p>I do <strong>not</strong> enforce deadlines for the purpose of trying
to simulate the “real world” or something like that. The deadlines are
for the purposes of maintaining smooth operation of the course.</p>
</section>
<section id="principle-empowering-students" class="level2">
<h2>Principle: Empowering Students</h2>
<p>My guiding principle for assignment logistics is that
<strong>students know better than I</strong> what is going on in their
life and how it affects their work. There are many different reasons a
student may need an extension on an assignment deadline, including but
definitely not limited to:</p>
<ul>
<li>illness</li>
<li>accident or emergency</li>
<li>last-minute child care needs</li>
<li>car got towed while they’re eating supper before finishing the
assignment</li>
<li>difficulty understanding material, need another trip to office
hours</li>
</ul>
<p>Further, I don’t really want to be in the position of adjudicating
what counts as a legitimate basis for an extension and what does not, or
obtaining backup justification, or anything like that. It isn’t fun,
it’s an unnecessary intrusion on students’ lives, it doesn’t advance
learning in any way, and it requires relatively arbitrary
decision-making.</p>
<p>Different students have different needs. So I want policies that will
meet a wide range of student needs, and empower students to do what they
need to in order to succeed in both the class and their lives.</p>
</section>
<section id="resources-for-success" class="level2">
<h2>Resources for Success</h2>
<p>This led me, when I was preparing my first syllabus in <em>Preparing
Future Faculty</em>, to frame the question differently: instead of rules
and exceptions, can I design the class to provide my students
<strong>resources</strong> that they can deploy as their particular
situation requires?</p>
<p>The fundamental concept I landed on here was to provide <strong>late
days</strong> as a resource. In their <a
href="https://cs533.ekstrandom.net/f22/syllabus/policies/#late-work">current
form</a>, they work as follows:</p>
<blockquote>
<p>For the assignments, you have a budget of 4 late days to use
throughout the semester, at your discretion.</p>
<ul>
<li>Each late day extends an assignment deadline by 24 hours with no
penalty.</li>
<li>Late days are indivisible, so submitting an assignment 12 hours late
uses an entire late day.</li>
<li>You may use up to 3 late days on a single assignment.</li>
</ul>
<p>When submitting an assignment using a late day, state with your
submission the number of days you are using. I appreciate it if you
notify me (via a Piazza private message) prior to the deadline that you
are planning to submit late, but do not require you to do so.</p>
</blockquote>
<p>I adjust the total number of late days based on the number of
assignments in the course and the level of the students; this version is
from a graduate course with 7 assignments. The number is relatively
arbitrary, but I’ve found 4 to generally work pretty well for such a
class. The limit on late days per assignment is to put a shorter
end-point on when all students have turned in an assignment. With
deadlines on Sunday night, a 3-day limit means I can teach through my
solution in class on Thursday.</p>
<p>The purpose of this design is to enable students to make the
decisions they need to in order to succeed, within a framework that
encourages them to think about those needs and select appropriate
tradeoffs. They could use most of their late days on an early assignment
just because they want more time, but that creates the risk of running
out of days later.</p>
<p>I round out this policy by also dropping their lowest assignment
score (or sometimes the lowest two scores, depending on the course). If
they’re out of late days, or something comes up that would require more
than the allowable limit, they can just blow off any one assignment
without penalty. Sometimes, when students talk with me about a late
assignment that they’re having difficulty finishing, I’ll encourage them
to just not submit it and save the late days for later. I have also, on
occasion, zeroed out a previously-submitted assignment and refunded the
late days for use on a later one.</p>
</section>
<section id="remaining-extensions" class="level2">
<h2>Remaining Extensions</h2>
<p>Late days and the dropped assignment cover the vast majority of needs
for extensions, so I very rarely grant extensions. About once every 2-3
terms a student will have needs beyond those accounted for in the base
course design, and I’ll work with them on a solution (additional
extensions, etc.), but for the standard extension requests I can point
students to the policy and they can take the extra time without needing
to justify themselves.</p>
</section>
<section id="alternatives" class="level2">
<h2>Alternatives</h2>
<p>I have experimented with escalating penalties for late work — 5% the
first day, then 10%, then 25% — but that has felt more punitive.
Resources that can be used without grade penalty is, in my opinion, a
more success-oriented approach to managing course deadlines.</p>
</section>
<section id="assignment-timing" class="level2">
<h2>Assignment Timing</h2>
<p>There’s also a lot of discourse sometimes about when assignments
should be due. I typically make my assignments due at midnight on
Sunday.</p>
<p>I use midnight as the deadline to allow students to work into the
evening while discouraging them from pulling all-nighters. When I’ve had
assignments due at the beginning of the next day (8 or 9 AM), I see
quite a few submissions at 4 or 5 AM. I’m lax on the precise time,
though, interpreting midnight as “before you go to bed”; if an
assignment is an hour or two late, I don’t count it as late.</p>
<p>The day is a little trickier. I’ve sometimes used other days, such as
Tuesday. If a course has class on Tuesday afternoon, I’ll sometimes make
the deadlines Tuesday at noon.</p>
<p>Some argue against Sunday deadlines, on the idea that it encourages
students to work on the weekends. I don’t find this argument persuasive,
as students have different needs and different lives. Many of our
students work during the week, especially upper-division and masters
students, and the weekend is the time they have set to work on their
course assignments. If I make the assignment on Friday, then students
who have plenty of time during the week have almost an entire week
longer to work on the assignments than the students who have to balance
their studies with a full-time job and family duties. In keeping with my
goal of empowering students to make the decisions they need for their
own lives, I put the deadlines either on Sunday night or early in the
week, with the expectation that if weekend work isn’t good for a
student, they can choose finish it on Friday, and the difference in
effective time for the assignment isn’t as large as it is if I used a
Friday deadline.</p>
</section>
<section id="student-response" class="level2">
<h2>Student Response</h2>
<p>Students have been overwhelmingly positive about this policy. There’s
sometimes a little confusion early on, or the first time a student
actually needs an extension (since they usually haven’t had the policy
in their other classes), but once I’ve explained the policy I have never
had a student complain about it. I have had a student say that all CS
courses should use my late day policy 😊.</p>
<p>If you find this policy useful, feel free to adopt it in your
courses. If you want to attribute it to me, a link to this blog post
would be fine. So far as I know, this particular policy was original or
at least independently developed; I don’t remember referencing any
directly similar policies while designing it, except perhaps some
general flexible extension policies, and was primarily informed by
universal design and what I was learning in class about how to
accommodate a wide range of student needs.</p>
</section>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Academic Portfolios]]></title>
        <id>https://md.ekstrandom.net/blog/2022/02/portfolio</id>
        <link href="https://md.ekstrandom.net/blog/2022/02/portfolio"/>
        <updated>2022-02-15T05:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="compact autosize right">
<a href="/images/stack-of-files.jpg"><img srcset="/images/stack-of-files-320.jpg 1x, /images/stack-of-files-480.jpg 1.5x, /images/stack-of-files-640.jpg 2x" src="/images/stack-of-files-640.jpg" alt="A stack of files." class="webfeedsFeaturedVisual"></a>
<figcaption class="credit">
Photo by
<a href="https://unsplash.com/@wesleyphotography?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Wesley
Tingey</a> on
<a href="https://unsplash.com/s/photos/files?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>
</figcaption>
</figure>
<p>A few years ago, early in my first faculty position, I wrote about <a
href="/blog/2015/10/organizing-work">how I tracked academic work</a>. I
still implement the core ideas of that piece, but most of the details
have changed; I thought now, after working on my first annual review
after applying for tenure, would be a good time to write an update.</p>
<aside class="update">
<strong>Updated Oct. 24, 2022:</strong> added a note about how I save
the rest of the teaching materials.
</aside>
<hr class="fold">
<section id="academic-review" class="level2">
<h2>Academic Review</h2>
<p>I’d like to start with a quick overview for new or prospective
academics. There’s the well-known Big Evaluation, when you apply for
tenure &amp; promotion; and a few years after that the application to
Full Professor. Many institutions have a pre-tenure reappointment review
at around year 3. You will also likely have one or two annual reviews;
for pre-tenure academics, these serve as a check-in on your tenure
progress, and for all academics they’re used for performance evaluation
and determining merit raises (in institutions that use the evaluation
for raises).</p>
<p>When I was at Texas State University, tenure-track faculty had to
provide data for two reviews each year, because the progress /
reappointment review and performance review were separate processes with
separate deadlines. It was weird.</p>
<p>But for each of these reviews, you need to provide a complete log of
your activities, in the format required by your institution (often in
addition to keeping it current on your CV). You also need supporting
documentation. What precise documentation is required will vary from
institution to institution, but you can’t go wrong by saving everything,
even if you don’t need to submit it all to the institution.</p>
</section>
<section id="current-cv" class="level2">
<h2>Current CV</h2>
<p>I keep my CV continuously updated as a part of my web site. Some of
the technical information is <a href="/colophon">here</a>; the web pages
for my publications serve as single-source-of-truth for publication
information, and my CV itself is a Markdown file that pulls in the
publication information, along with other content, for rendering to
HTML. WeasyPrint converts that to a <a href="/cv.pdf">PDF</a> that I can
save, share, upload, etc.</p>
<p>Some institutions require you to highlight new things. Texas State
did that; I dealt with that by including the year in an HTML class or
<code>data</code> attribute on the relevant HTML <code>&lt;li&gt;</code>
elements, and a bit of CSS to recolor everything for a particular year.
These days I would probably just mark up the PDF in Acrobat.</p>
<p>Keeping my CV current as I get new publications means my annual CV
update is mostly adding new teaching &amp; updating my service. Huge
time-saver at review time; time sink overall, but it’s my pet time sink
and I’m rather proud of it.</p>
</section>
<section id="logging-work" class="level2">
<h2>Logging Work</h2>
<p>Different types of work have different logging needs. Paper
acceptances sort of log themselves, but you need to keep track of
service work, grant applications, teaching, etc. I have different logs
for different bits:</p>
<ul>
<li>All reviewing work is logged in a DynaList notebook, by type; this
is also where I keep track of deadlines and my overall reviewing
workload.</li>
<li>I have a spreadsheet for grant proposals that lets me look at my
overall funding rate, etc.; I included a copy of this in my tenure
application.</li>
<li>Teaching I just update in my CV every year; our faculty effort
reporting system automatically gets it from the registrar’s office.</li>
</ul>
<p>Many universities have online systems for tracking work; Boise State
uses Faculty 180. You will need to enter your work into this system for
review, but I find it completely useless for my own use. I recommend
keeping your own records, and then updating the effort tracker at review
time. This lets you keep them in a format that’s useful for you; also,
you can keep copies of them if you ever need them for a dispute, you get
fired, or you’re looking for a different position. Keeping your own
copies of vital professional records (so long as no FERPA or
otherwise-protected information is implicated) on your own private drive
is good practice.</p>
</section>
<section id="documenting-work" class="level2">
<h2>Documenting Work</h2>
<p>I maintain a <code>Portfolio</code> folder that I use for documenting
all my work: copies of my annual review reflections, documentation for
my accomplishments, copies of my CV each year, tenure application
materials, and whatever else is relevant to documenting my professional
career. I’ve gone back and filled it in all the way back to the
beginning of my Ph.D.</p>
<p>This folder is organized by year (calendar year, since we do annual
reviews on calendar year cycles at Boise State). Within each year, I
have several things:</p>
<ul>
<li>That year’s reflections from my annual eval (Word documents)</li>
<li>End-of-year CV</li>
<li>A <code>Papers</code> directory with PDFs from all papers published
(or in some cases accepted) that year</li>
<li>A <code>Proposals</code> directory with every grant proposal
submitted</li>
<li>A <code>Teaching</code> directory with saved teaching resources
<ul>
<li>Syllabi</li>
<li>Student evals</li>
<li>Representative documents (assignments, final exam, project
description, etc.)</li>
</ul></li>
<li>A <code>Service</code> directory with documentation &amp; evidence
for major service assignments (e.g. thank-you letters for program
committees, etc.). If the only evidence available has confidential
information (such as the titles of papers or proposals reviewed), I
print it to a PDF and use Acrobat’s Redact tool to remove the private
info.</li>
<li>A <code>Talks</code> directory with documentation for invited
talks.</li>
<li>Other files to document random things (like my ACM Senior Member
designation).</li>
</ul>
<p>After receiving a year’s evaluations (from chair, tenure committee,
dean, etc.), I save PDF copies into the year’s directory as well.</p>
<p>I try to drop things into this folder as they happen, but usually
need to spend an hour or two each year reviewing it and hunting down any
missing files. Making sure it’s up to date as a part of my annual eval,
though, means it’s already there and I don’t need to check for missing
things when preparing for one of the big evaluations.</p>
<p>In addition to the portfolio documents, I also have each class in a
separate working folder, organized by year and term, and keep those
indefinitely. All documents I create for the class, except things like
source code and web content that live in a GitHub repository, are there,
and I keep them indefinitely. If I need another document from a
particular class, it’s almost always in that folder, the class GitHub,
or Canvas (and most of my content on Canvas is links to documents or web
pages, very little except exams lives natively there).</p>
<p>A little organization in advance can save a headaches later.</p>
</section>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[What Is a Dissertation?]]></title>
        <id>https://md.ekstrandom.net/blog/2022/02/dissertation</id>
        <link href="https://md.ekstrandom.net/blog/2022/02/dissertation"/>
        <updated>2022-02-08T05:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="compact autosize right">
<a href="/images/loose-manuscript.jpg"><img srcset="/images/loose-manuscript-320.jpg 1x, /images/loose-manuscript-480.jpg 1.5x, /images/loose-manuscript-640.jpg 2x" src="/images/loose-manuscript-640.jpg" alt="A loose manuscript." class="webfeedsFeaturedVisual"></a>
<figcaption class="credit">
Photo by
<a href="https://unsplash.com/@towfiqu999999?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Towfiqu
barbhuiya</a> on
<a href="https://unsplash.com/s/photos/manuscript?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>
</figcaption>
</figure>
<p>If we can aggressively simplify for a moment, earning a Ph.D has
three primary components:</p>
<ul>
<li>Do research.</li>
<li>Write it up in a dissertation.</li>
<li>Convince a committee of faculty that what you’ve done and presented
is worthy of a research-based terminal academic degree.</li>
</ul>
<p>There are some other things in each program, such as courses and
qualifiers, but this is the heart of what earns the degree.</p>
<p>But what <em>is</em> that mysterious “dissertation”?</p>
<hr class="fold">
<p>It’s a full report on a body of research that is sufficient to
demonstrate competence as an independent researcher and earn a Ph.D: an
original contribution to knowledge in your field. Matt Might has a good
<a
href="https://matt.might.net/articles/phd-school-in-pictures/">illustrated
guide</a> to what it looks like to create new knowledge, and how that
relates to earlier academic training.</p>
<p>But that still leaves the question about the dissertation
<em>itself</em>: what should such a document contain, and how should it
be organized? When you do your dissertation proposal, what are you
actually proposing to do?</p>
<p>That’s what I hope to address in this post.</p>
<aside class="callout green">
The following is my own opinion, based on discussions with my colleagues
and my experience earning a Ph.D, advising students, sitting on
committees, and reading others’ dissertations, in computer and
information science in the United States. Expectations may well differ
for other fields and other nations, but this is pretty consistent with
what I see in European dissertations, and is likely applicable at least
to other science and engineering fields. Of course, if your committee
has expectations that disagree with what I’ve written here, listen to
them!
</aside>
<section id="scope" class="level2">
<h2>Scope</h2>
<aside class="callout credit magenta">
Most of this section is things I learned from <a
href="https://konstan.umn.edu/">Joe Konstan</a>’s <em>Introduction to
Research in Computer Science</em> (UMN CSCI 8001/8002). The presentation
and any bad ideas are my own, but that’s where I learned this model of
scoping.
</aside>
<p>Before we get to the organization of the dissertation document
itself, I want to spend just a little time on the scope of a
dissertation.</p>
<p>A dissertation should contain the research content of approximately
three good papers (full conference or journal papers). There are
variations on this — sometimes there’s 4 papers, sometimes two short
papers replace a full paper — but it’s the basic idea. Not all papers
need to be published: at least one should, but the publication process
is fickle sometimes. The ideal is probably to have one published,
another published or accepted, and a third under review (or in
preparation for a deadline shortly after defense).</p>
<p>These three papers will also often not be the only papers you write
in the course of your Ph.D — while additional papers are not needed to
earn the degree, they’re usually necessary to have a competitive
application when pursuing research-oriented jobs.</p>
<p>The 3–4 papers should also be on a theme so you can tell a coherent
story of your dissertation work. Three disconnected papers on different
topics are hard to sell, and make it difficult for you to make a clear
pitch of research directions when you’re on the job market. There isn’t
one story for a dissertation, but there are a few general shapes that
tend to work well:</p>
<dl>
<dt>The Hammer</dt>
<dd>
A “hammer” dissertation focuses on a particular toolset and demonstrates
its applicability across a range of problems; each paper is applying a
similar tool to a different problem, and the overall synthesis draws the
resulting knowledge together to show where the tool does and does not
work, what’s needed to make it effective, and how to adapt it to a range
of settings. You have a hammer, and you show that it can pound ordinary
nails, U-shaped nails, and some weird five-pointed thing.
</dd>
<dt>The Problem</dt>
<dd>
A “problem” dissertation focuses on a particular problem or problem area
and does a deep multi-paper exploration. The papers may apply different
techniques to the same problem, or they may examine different stages of
the problem or different perspectives on it. The key thing here is that
the problem, rather than the tools, are the unifying theme: in the
analogy you’re trying to study the problem of fastening things to wood,
and study using a hammer and nails, a screwdriver, and glue. Or maybe
you study different things to do with wood and similar materials, like
pounding, carving, and painting.
</dd>
<dt>The Hybrid</dt>
<dd>
A hybrid dissertation is between the problem and the hammer: it focuses
primarily on one application or problem for 2 of the papers, and spends
the third showing that the techniques and/or knowledge are also
applicable to a related problem. You focus on the problem of fastening
things to wooden objects, and then show the hammer can also be used for
something else.
</dd>
</dl>
<p>There are likely other workable designs as well, but most coherent
stories for 3–4 papers will probably fit one of these patterns, more or
less.</p>
</section>
<section id="dissertation-outline" class="level2">
<h2>Dissertation Outline</h2>
<p>So you have some papers, and an overall narrative to show how they
form a connected and coherent body of work. What does the actual
document look like?</p>
<p>There are some variations, but I expect most dissertations I advise
to have an outline approximately like this:</p>
<ol type="1">
<li><p><strong>Introduction.</strong> The first chapter casts your
overall vision: defines your topic and the terms needed to understand
it, presents your story, and previews your contributions. In particular,
it sets up your organizing theme (either the hammer or the problem
you’re solving). By the end of it, the reader should know (1) what
you’re trying to do (including your organizing principle), (2) why it
matters, and (3) your core contributions. The rest of the dissertation
is to then convince them that you actually make the contributions you
claim.</p></li>
<li><p><strong>Background &amp; Related Work.</strong> The second
chapter is your primary literature survey. This serves two distinct but
related roles<a href="#fn1" class="footnote-ref" id="fnref1"
role="doc-noteref"><sup>1</sup></a>: first, it covers the necessary
background for a reader who is competent in computer science broadly,
but not your specific specialty, to understand the rest of your work.
Second, it positions your dissertation work in the broader research
space, and in particular other work on your problem and related or
precursor problems.</p>
<p>This is the literature survey for your <em>whole dissertation</em>.
Some later chapters may also contain small background and/or related
work sections that survey work specifically supporting that chapter’s
unique work, but the common elements should usually be factored out into
Chapter 2. In some dissertations, you may present most of the background
in Chapter 1, so Chapter 2 is just related work, but in my experience
there’s still background that’s needed in Chapter 2.</p></li>
<li><p><strong>Common Infrastructure</strong> (optional). In some
dissertations, you’ll have some resource, such as software or a data
set, that you use throughout the entire dissertation. If it doesn’t make
sense to describe it in Ch. 2, it can be useful to spend Chapter 3
describing this resource in some detail. In some cases, if it is an
original resource, it may also be a paper, particularly if your
discipline has venues for publishing resources such as the SIGIR
Resource Track or the NeurIPS Datasets &amp; Benchmarks track. Many
dissertations won’t have a dedicated chapter for this, though.</p></li>
<li><p><strong>Research Content.</strong> The next 3–4 chapters present
your primary research content. Each of your component papers usually
becomes a chapter; much of the content can be reused from the paper, but
you usually need to make a few changes:</p>
<ul>
<li>Rewrite the intro so it flows narratively as a chapter of a book
rather than a standalone paper, including discussions of how it relates
to the other chapters (particularly the previous chapters)</li>
<li>Rewrite the background and/or related work so that any ideas shared
with the other papers in the dissertation are moved to Chapter 2, and
the content chapter only has background that’s specific to that
chapter’s methods (or the methods that are introduced for use in later
chapters). You don’t want to introduce the same related work in three
different chapters.</li>
<li>Expand the writing to include useful details that you had to drop
from the published version for length reasons, further charts and
results that shed deeper insight on your findings, etc. In some cases,
you may need to re-run or update experiments, particularly if your
methods have improved in later portions of the work. What this looks
like will differ a lot between dissertations; in my own, Chapter 3
described our research software, which evolved quite a bit from the
first published version to what was released as I finished grad school,
so I largely rewrote that chapter to describe the software as it was at
the end, not the first version.</li>
</ul></li>
<li><p><strong>Conclusion.</strong> Your last chapter ties it all
together: given the vision outlined in Ch. 1, and the work presented in
the research content chapters, what do we know about your topic now that
we didn’t know before you started the Ph.D? What are the next steps to
advance knowledge beyond what you’ve accomplished in the
dissertation?</p></li>
<li><p><strong>Appendices.</strong> Some dissertations have appendices;
their use varies. I’ve seen them used for additional research content
outside the main narrative flow, such as another paper the student
wrote. They’re also useful for additional supporting evidence for the
research content that would break the flow too much if you included it
in the chapter, but you want to make available to readers who wish to
check your work more thoroughly. This can include documentation for
software you developed, more complete output from statistical models,
supplementary charts, etc. If anything is needed to understand one of
your research results, however, it should go in the main chapter,
<strong>not</strong> an appendix.</p></li>
</ol>
<p>There are some variations on the themes — I didn’t have a 1:1
relationship between papers and chapters in my own dissertation — but
for a typical computer science dissertation, this outline will usually
work pretty well, and strikes a useful balance (in my opinion) between a
pure staple dissertation that does no integration, and a complete
rewrite of all the material.</p>
</section>
<section id="planning" class="level2">
<h2>Planning</h2>
<p>When you’re planning out your dissertation work, particularly around
the proposal stage, that’s what you’re planning to write. Make sure you
leave plenty of time for the writing — it can take longer than you
expect, and while the dissertation doesn’t need to be your best writing,
it should be reasonably good and definitely needs to be clear and
readable.</p>
<aside class="callout purple credit">
Thanks to Sole Pera for providing feedback on a draft of this post. All
errors and bad ideas are, of course, mine. Further feedback is
<a href="/contact">welcome</a>.
</aside>
</section>
<section id="footnotes" class="footnotes footnotes-end-of-document"
role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>Thanks to Sole Pera for frequently reminding me not to
blur these roles.<a href="#fnref1" class="footnote-back"
role="doc-backlink">↩︎</a></p></li>
</ol>
</section>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[2021 State of the Tools]]></title>
        <id>https://md.ekstrandom.net/blog/2021/12/tools</id>
        <link href="https://md.ekstrandom.net/blog/2021/12/tools"/>
        <updated>2021-12-30T05:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="right compact autosize">
<a href="/images/bicycle-tools.jpg"><img srcset="/images/bicycle-tools-320.jpg 1x, /images/bicycle-tools-480.jpg 1.5x, /images/bicycle-tools-640.jpg 2x" src="/images/bicycle-tools-640.jpg" alt="A sillouette of a bicycle with two tools"></a>
<figcaption class="credit">
Photo by
<a href="https://unsplash.com/@tommy_c137?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Takehiro
Tomiyama</a> on
<a href="https://unsplash.com/s/photos/bicycle-tools?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>
</figcaption>
</figure>
<p>It has been a couple of years, hasn’t it? I thought I would do
another <a href="/blog/tags/tools">state of the tools</a>. Last year I
had other things on my mind and didn’t get to this kind of thing, but
let’s give it a whirl.</p>
<section id="things-ive-kept" class="level2">
<h2>Things I’ve Kept</h2>
<p>My stack has stabilized quite a bit over the last few years; many
things are the same, or slightly upgraded, from <a
href="/blog/2019/12/tools">2019</a>. Highlights:</p>
<ul>
<li>Surface devices for portability (Pro 4 at home, Go 2 at work). In
2020 I upgraded from the original Go to the Go 2, which is significant
upgrade in performance.</li>
<li>Dell desktops (2016 XPS i7 at home, 2019 Precision i9 at work)</li>
<li>iPhone 13 Mini and Apple Watch (I upgraded from the SE to the 11 Pro
at the beginning of 2020, and am now on the 13 Mini; only had it for a
few days but quite happy with it).</li>
<li>Windows 10 on the client (with Windows 11 on my Go 2).</li>
<li>RHEL 8 on the work server.</li>
<li>Google for work e-mail; Office 365 for personal e-mail, docs, and
storage.</li>
<li>Chrome for work-related browsing (linked to university Gmail).</li>
<li>Signal and WhatsApp for most personal communication.</li>
<li>Slack and Hangouts for work messaging.</li>
<li>Word and Google Docs for writing; Paperpile for citation
management.</li>
<li>Python for most programming; Rust for targeted speed improvements,
JavaScript for web-related things.</li>
<li>Editing in VS Code almost exclusively; a little Nano in the
terminal.</li>
<li>Git and DVC for code and data management.</li>
<li>PostgreSQL whenever I need a relational (or JSON) database.</li>
<li>Paint.net for lightweight image edits.</li>
<li>Grapholite and Visio for diagraming.</li>
<li>Drawboard PDF for PDF reading and markup.</li>
<li>1Password for password management.</li>
<li>Leuchtturm1917 A5 hardbound notebooks and a collection of fountain
pens.</li>
</ul>
</section>
<section id="small-changes" class="level2">
<h2>Small Changes</h2>
<ul>
<li>Switched from Edge back to Firefox for personal browsing. Edge
doesn’t scale well to large numbers of tabs, and I believe Microsoft’s
shopping assistant is actively harmful to small Internet
businesses.</li>
<li>iPhone upgrade mentioned above.</li>
<li><a
href="https://github.com/buptczq/WinCryptSSHAgent">WinCryptSSHAgent</a>
for YubiKey/SSH support. Considering moving to PuTTY-CAC</li>
<li>Increased usage of Rust for data processing.</li>
</ul>
</section>
<section id="graphics-and-document-software" class="level2">
<h2>Graphics and Document Software</h2>
<p>I still use Paint.net for lightweight raster image editing, but have
switched to Serif’s <a href="https://affinity.serif.com/en-us/">Affinity
suite</a> for more substantial work (Photos for raster images, Designer
for vector images, and Publisher for advanced document preparation).
They’re much less expensive than Adobe’s suite, seem generally more
robust and easier to use than the open-source ones I had ben using
(Krita, Inkscape, and Scribus).</p>
<p>I’ve also switched from FoxIt Pro to Adobe Acrobat Pro for PDF
manipulation at work, now that Boise State has an Acrobat site
license.</p>
</section>
<section id="bicycling" class="level2">
<h2>Bicycling</h2>
<p>My Novara Gotham commuter bike suffered frame failure mid-fall. I am
replacing it with a Tout Terrain Amber Road (frame-only, will build up
with the components from the Gotham, several of which I had upgraded).
The result will be a steel-frame commuter with an Alfine 8-speed
drivetrain with dynamo hub and Gates carbon drive.</p>
<p>I’m still using the Wahoo ELEMNT Bolt for my road bike rides, but
have switched from Strava to RideWithGPS for my cycle tracking. The
ELEMNT Mini didn’t work out, both because it wasn’t very reliable, and
Wahoo’s software update on the app seemed to break the Mini after they
discontinued the product; rather than trying another dedicated computer
and hoping it would work, I got a QuadLock handlebar mount to just use
my phone for mapping and logging. However, on my commuter, it’s pretty
crucial to be able to quickly see the current time along with speed and
ride statistics, so I can see how long I have until an appointment at my
destination. Strava doesn’t display a clock and doesn’t allow the live
interface to be customized. RideWithGPS has a customizable live display
including a clock.</p>
</section>
<section id="home-network" class="level2">
<h2>Home Network</h2>
<p>I’ve standardized my home network devices (NAS and media widget) on
Alpine Linux.</p>
<p>The “media widget” is a Raspberry Pi Zero W with a <a
href="https://www.hifiberry.com/shop/boards/hifiberry-dac-zero/">HiFiBerry
DAC+ Zero</a> attached, using <a
href="https://github.com/librespot-org/librespot">librespot</a> to play
Spotify on our stereo. It also has shairport-sync installed for AirPlay,
but we don’t actually use that very often. I tried Raspbian on it for a
while, but had update problems with a slow SD card; upgrading the SD
card fixed that, but switching to Alpine in “data” mode has worked very
well and has a pretty simple upgrade path that doesn’t tax the Pi Zero’s
meager processor as much. With only minimal necessary software
installed, the system takes about 50–60 MiB of memory, which leaves
enough of the board’s 512 MiB free for system operations. I packaged
(and maintain) librespot for the Alpine testing repository, so the
install and updates are generally pretty smooth. I am very intrigued by
the <a
href="https://www.raspberrypi.com/products/raspberry-pi-zero-2-w/">Pi
Zero 2 W</a>, but am not in a hurry to upgrade.</p>
<p>The NAS is still our old QNAP 2-bay NAS, running Alpine for
consistency across our network (and to give an environment with more
resources to test Alpine things for use on the Zero). I don’t actually
use the NAS for much anymore; I was using it to backup teaching
materials, but need to find new software to facilitate that since the
Boise State network blocks Syncthing for unknown reasons. If and when it
dies, I don’t know if I will replace it or not; would probably do
something on a 64-bit Pi platform.</p>
</section>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Yay Reproducibility]]></title>
        <id>https://md.ekstrandom.net/blog/2021/11/reproducibility</id>
        <link href="https://md.ekstrandom.net/blog/2021/11/reproducibility"/>
        <updated>2021-11-19T05:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="right autosize compact">
<a href="/images/bug-assembly-line.jpg"><img srcset="/images/bug-assembly-line-320.jpg 1x, /images/bug-assembly-line-480.jpg 1.5x, /images/bug-assembly-line-640.jpg 2x" src="/images/bug-assembly-line-640.jpg" alt="An assembly line of VW bugs"></a>
<figcaption class="credit">
Photo by
<a href="https://unsplash.com/@austriannationallibrary?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Austrian
National Library</a> on
<a href="https://unsplash.com/s/photos/assembly-line?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>
</figcaption>
</figure>
<p>One of my projects this year has been to refactor and upgrade our <a
href="https://bookdata.piret.info">Book Data Tools</a> to be easier to
work with. Along the way I found some bugs in the original data
integration, and the process of finding these bugs and assessing their
impact forms a useful case study for some of the principles I teach in
my <a href="https://cs533.ekstrandom.net/f21/week7/">data science
class</a>.</p>
<section id="current-state" class="level2">
<h2>Current State</h2>
<p>The current published version of the tools uses PostgreSQL for data
storage and integration, which is pretty good in principle but has
significant impedance mismatch with my use of <a
href="https://dvc.org">DVC</a> to automate the data processing and
integration process. It has an ugly set of hacks involving status files
and two DVC stages for almost every logical stage of the data
integration pipeline (because my other egregious hack of monkey-patching
DVC to support a custom URI schema to track PostgreSQL data status as if
it were external data broke with DVC upgrades). The result was a
repository layout and set of program interactions that are very
non-obvious, so it’s harder than it needs to be for others to extend the
tools.</p>
<p>It’s also harder than it needs to be to manage different version of
the data, as we have to have different PostgreSQL databases, keep them
straight (and synchronized with Git branches), and it all takes a lot of
space.</p>
<p>PostgreSQL has served us well, but it’s time to move on.</p>
</section>
<section id="upgrade-pathway" class="level2">
<h2>Upgrade Pathway</h2>
<p>In the time since I built the original data integration, the Apache
Arrow project has made significant progress on the Rust version of
Arrow, along with the <a
href="https://github.com/apache/arrow-datafusion">DataFusion query
engine</a> that supports SQL queries (with a lot of PostgreSQL syntax)
over Parquet files.</p>
<p>I have used this to rebuild the integration primarily in Rust and
SQL, using custom Rust code for data processing and DataFusion to
implement many of the integrations. This fits much better with DVC’s
file-based input/output model, as each stage reads input files (from
CSV, compressed JSON, MARC, an intermediate Parquet file, or whatever)
and produces output in CSV or Parquet format. We can use DVC as
intended, without needing to document our own weird pipeline quirks on
top of the data structures and integration logic itself.</p>
<p>The result is much faster (2 hours instead of 12), takes less space,
and removes both the impedance mismatch and some of the complexity of
installation (as users no longer require a PostgreSQL installation). It
does mean extending it usually requires writing some new Rust, but we’re
generally using our dependences as they’re intended to be used, which
should help a lot with that learning curve.</p>
</section>
<section id="spotting-problems" class="level2">
<h2>Spotting Problems</h2>
<p>One of the things I teach my students about data integration is to
always track the coverage or the success rate of each link in their
integration pipeline. I do that in this project: for each source of book
or rating data, I measure the fraction of records that fail along each
stage of the pipeline:</p>
<ol type="1">
<li>Can’t find a book</li>
<li>Book doesn’t have authors</li>
<li>Can’t find information about authors</li>
<li>Author doesn’t have a gender record</li>
</ol>
<p>This is all reported in the <a
href="https://github.com/BoiseState/bookdata-tools/blob/master/LinkageStats.ipynb">statistics
notebook</a>, and allows us to have a good view of the data
coverage.</p>
<p>I watched these statistics closely while I was reworking the code,
and ensuring we had (approximately) the same coverage was one of the
major criteria for making sure I hadn’t broken the logic.</p>
<p>When I was pretty much done, though, I had a problem: the GoodReads
coverage was broken. All my other coverages (Library of Congress,
BookCrossing, and Amazon) were fine — slightly better, actually, because
I had improved some of the ISBN parsing and resolution logic, and had
updated the source data to use more current versions of OpenLibrary and
VIAF — but GoodReads had gone from about 58% unresolved books to 68%. I
fixed two or three other bugs (including working around an annoying bug
in the Rust implementation of Parquet) to get there, but the GoodReads
coverage wouldn’t budge.</p>
</section>
<section id="finding-the-problem" class="level2">
<h2>Finding the Problem</h2>
<p>After digging around for a while (the details are rather boring), I
finally found the problem: the original PostgreSQL integration code had
a bug in the GoodReads interaction data integration, where I used an
inner join where I should have used a left join. The result is that if a
GoodReads book did not have an ISBN, its ratings and interactions were
dropped entirely, instead of included as interactions with unknown
books. A lot of these are either older books or Kindle exclusives (or
editions that haven’t been linked with their works yet); it was a rather
large number of books, but they don’t seem among the more active
books.</p>
<p>I verified this by replicating the erroneous join logic in the new
code, and my coverage jumped back up to where it was supposed to be.
Glad I found the problem, but disappointingly it was due to a bug in the
version that supported the published results. Once verifying the bug I
reverted the change, because including all the records is the correct
decision for our purposes.</p>
</section>
<section id="how-big-is-the-problem" class="level2">
<h2>How Big is the Problem?</h2>
<p>This data integration is the data set backing our signature <a
href="/pubs/bag-extended">book gender paper</a>. Since the bug only
affected books that didn’t have links elsewhere in the database, and
most of the analysis focuses on books for which we have data on the
author’s gender, I expected it wouldn’t have much impact on the
published results: it would affect our GoodReads coverage statistics,
and the collaborative filters would be trained on a larger data set (one
that includes interactions with these unknown books).</p>
<p>But we need to make sure.</p>
<p>The paper is supported by <a
href="https://github.com/BoiseState/book-author-gender">reproduction
scripts</a> that use DVC to automate the entire experiment process: run
<code>dvc repro</code> on a large machine, and a day or three later
you’ll have the results. This has had quite a few advantages, including
allowing us to do a clean re-run and report the compute resources needed
to reproduce the experiments in the paper. My team is also using this
repository as the basis for further research, so I have been working on
upgrading it to use the new version of the data integration instead of
pulling from the PostgreSQL database.</p>
<p>So, I imported the new data into the book gender experiment and
re-ran it.</p>
<p>As I hoped, the results stayed the same. The coverage plots changed
to reflect the new data coverage, but nothing else changed beyond the
expected variance due to randomization.</p>
<p>Sigh of relief.</p>
</section>
<section id="lessons-learned" class="level2">
<h2>Lessons Learned</h2>
<p>I spend a lot of time polishing data and experiment pipelines —
sometimes too much time — but the result here was finding an interesting
bug, correcting it, and being able to verify that it didn’t invalidate
any of our experimental results.</p>
<p>There are many reasons why reproducible pipelines are important, but
this is one. It takes time and effort to achieve, but the resulting
improvements in robustness and confidence in results are worth
significant investment, in my opinion.</p>
</section>
<section id="next-steps" class="level2">
<h2>Next Steps</h2>
<p>I still have some more polishing and documentation to do before
releasing the new code, but I plan to update the public version of book
data tools to use the new system shortly. I’ll also be pushing a new
version of the scripts for the paper that reproduce its results using
the current integration, as an example of how to use it. Stay tuned!</p>
</section>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Ritual]]></title>
        <id>https://md.ekstrandom.net/blog/2021/02/ritual</id>
        <link href="https://md.ekstrandom.net/blog/2021/02/ritual"/>
        <updated>2021-02-07T05:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="right compact autosize">
<a href="/images/whiskey-notebook.jpg"><img srcset="/images/whiskey-notebook-320.jpg 1x, /images/whiskey-notebook-480.jpg 1.5x, /images/whiskey-notebook-640.jpg 2x" src="/images/whiskey-notebook-640.jpg" alt="Whiskey and my notebook" class="webfeedsFeaturedVisual"></a>
</figure>
<aside class="nav series">
<p>This is the ninth article in my <a
href="/blog/series/productivity">series on productivity</a>.</p>
</aside>
<aside class="callout tldr">
Regular routines help me plan my work, use my tools, and sleep better
going into a new week.
</aside>
<p>Ritual is a powerful tool for navigating life. David Allen, in
<em>Getting Things Done</em>, promotes a weekly review to take stock of
your current Next Action lists, identify projects that need to be moved
up (or down) the stack, and plan what’s next.</p>
<p>Regular rituals are an important part of my work management, linking
together my <a href="/blog/2017/02/productivity-notebook">notebook</a>,
<a href="/blog/2021/02/runway">runway</a>, and <a
href="/blog/2017/02/productivity-self-care">self-care</a>. My particular
practices are modeled most closely after the tactical practices of
implementing Michael Linenberger’s <a
href="https://www.michaellinenberger.com/free1MTD.htm"><em>One Minute
To-Do List</em></a> on paper.</p>
<hr class="fold">
<section id="the-week" class="level2">
<h2>The Week</h2>
<figure class="right compact autosize">
<a href="/images/week-chart-2021.jpg"><img srcset="/images/week-chart-2021-320.jpg 1x, /images/week-chart-2021-480.jpg 1.5x, /images/week-chart-2021-640.jpg 2x" src="/images/week-chart-2021-640.jpg" alt="Week plan in my notebook"></a>
<figcaption>
Weekly plan. They’ve gotten more colorful than my initial presentation.
</figcaption>
</figure>
<p>The most central of my rituals is on Sunday evenings. I make a
practice of not working on Sundays; in the morning we go (or “go”,
during COVID) to church, and in the afternoon I relax, often with a
video game.</p>
<p>Sunday evening I begin to prepare for the next week. I fetch my <a
href="/blog/2017/02/productivity-notebook">notebook</a> and my <a
href="/blog/2020/03/pens">pens</a>, typically pour myself a whiskey
(tonight I’m drinking a bourbon from Wyoming Whiskey), and reflect on
what I need to do the next week. If I’ve been able to rest over the
weekend, my mind is starting to think of the next week’s work, and
committing those thoughts to paper helps me put them aside (“this is for
tomorrow, and it’s written down so I won’t forget”) and start the week
with better rest; if I haven’t, it helps me get mentally ready for the
week ahead.</p>
<p>I use this time to fill out my <em>Weekly Plan</em> and the (initial)
To-Do and Scheduled sections of my <em>Daily Log</em> for Monday (see my
<a href="/blog/2017/02/productivity-notebook">notebook description</a>
for more details on what these contain). I consult both my calendar and
my <a href="/blog/2021/02/runway">runway</a> when planning this, to see
if there are things coming up that I need to touch this week, if only to
check on their status. This practice helps me contain my thoughts, and
makes it easier to go to sleep Sunday night knowing that I’ve written
down the things I remembered over the weekend that I should do, and I
have a plan of action to begin the week. If I don’t make my plans on
Sunday, it’s easy to lie awake pondering the things I need to do. Monday
morning, I can wake up and meet the day.</p>
</section>
<section id="daily-to-dos" class="level2">
<h2>Daily To-Dos</h2>
<p>I operate day-to-day with a <em>Daily Log</em>. Before beginning the
day’s work (or occasionally after a first-thing meeting), I write down
my schedule and a set of Desired Outcomes for the day. The details of
this ritual are a little more variable; it’s usually in the morning, but
I also commonly do it as I wrap up work the day before. The day before
option is often helpful when I’m feeling behind, as making a plan for
the next day’s work makes it easier to call today’s work done.</p>
</section>
<section id="re-copying-lists" class="level2">
<h2>Re-Copying Lists</h2>
<p>In both my daily and weekly work planning, I am regularly writing
down the same items, either because they are recurring, or because they
didn’t get done the first time. This physical act of copying them, as
noted by <a
href="https://99u.adobe.com/articles/6915/how-analog-rituals-can-amp-your-productivity">Bob
Greenberg</a>, helps me feel connected to my work, and forces a regular
implicit review of what exactly it is that I’m trying to do.</p>
<p>I’ve used various digital to-do lists, including the rather flexible
auto-prioritization logic of TaskWarrior, and for me, nothing beats
regular, tangible contact with my workload.</p>
</section>
<section id="longer-term-rituals" class="level2">
<h2>Longer-Term Rituals</h2>
<p>At the beginning of each semester, I take stock of what my goals are
for the term, what I hope to accomplish for myself and my students. I
review and update my <a href="/blog/2021/02/runway">runway</a>, breaking
out the new term’s months if needed and assessing the planned and
committed work.</p>
<p>I used to do annual plans as well, but with the runway giving me a
continuous big-picture view, the semester plans seem adequate to the
role the annual review used to serve. For a few years, I had a practice
of writing a blog post at the end of the year summarizing what I had
accomplished with links to public outcomes (see e.g. <a
href="/blog/2018/12/2018">2018</a>); I may resume this practice in the
future.</p>
</section>
<section id="conclusion" class="level2">
<h2>Conclusion</h2>
<p>Regular, often physical, contact with my workload helps me keep on
top of what I need to do. It also helps my mental and emotional health,
as planned times to review my work and plan the next week let me offload
things occupying (anxious) brain cycles and rest soundly in the
knowledge that they’re captured in a form more reliable than neurons.
When I’m feeling particularly underwater, the act of writing down what I
need to do helps me find my way out and think, concretely, about what to
do next.</p>
<p>Different things will work for different people, but I find analog
rituals to be crucial and these are ones that work for me at this time
in my life. In a year or five I may well be doing something differently.
We will see what the future brings.</p>
<p>If you’re feeling completely overwhelmed and having difficulty
planning your way through the next day, I found <a
href="https://www.michaellinenberger.com/free1MTD.htm">One Minute To-Do
List</a> helpful in that situation — it starts with a short analog
ritual, and provides useful tactics for taking back some control of your
workload.</p>
</section>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[The Runway — Planning the Next Thing]]></title>
        <id>https://md.ekstrandom.net/blog/2021/02/runway</id>
        <link href="https://md.ekstrandom.net/blog/2021/02/runway"/>
        <updated>2021-02-04T05:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="right">
<a href="/images/runway.jpg"><img srcset="/images/runway-320.jpg 1x, /images/runway-480.jpg 1.5x, /images/runway-640.jpg 2x" src="/images/runway-640.jpg" alt="Lit runway"></a>
<figcaption class="credit">
Photo by
<a href="https://unsplash.com/@jmoncasi?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Jordi
Moncasi</a> on
<a href="https://unsplash.com/s/photos/runway?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a>
</figcaption>
</figure>
<aside class="nav series">
<p>This is the eighth article in my <a
href="/blog/series/productivity">series on productivity</a>.</p>
</aside>
<aside class="callout tldr">
I use a “runway” document to plan my work on a multi-semester timeframe.
</aside>
<p>In “<a href="/blog/2017/04/productivity-holes">Holes</a>”, I noted
that one of the holes in my work planning and management is a good tool
for tracking and planning upcoming work over time. I have now filled
that hole with my <em>Runway Document</em>, which also replaces “<a
href="/blog/2017/02/productivity-the-wall">The Wall</a>”.</p>
<hr class="fold">
<section id="runway-organization" class="level2">
<h2>Runway Organization</h2>
<p>I keep my runway in a document in <a
href="https://dynalist.io">DynaList</a>, an online outliner (similar to
Workflowy but with multiple separate files). I have a section for each
upcoming term (spring, summer, and fall), at least through the next
year. The current term, and occasionally the next term, is broken down
with a subsection for each month.</p>
<p>Within these sections, I keep a list of the things coming up that
month. Most of these are deadlines or commitments, but some are other
planned activities or ticklers (e.g. a reminder to re-activate a
currently dormant project). If something has a known deadline, I include
that (as a Dynalist date).</p>
<p>When something is done, I mark it as completed; at the end of a
period, I mark the whole period (month or term) completed after dealing
with the unfinished items (moving them to a later period or deleting
them if they are no longer relevant).</p>
</section>
<section id="adding-to-the-runway" class="level2">
<h2>Adding to the Runway</h2>
<p>Whenever I commit to something with a time period, like a talk or a
conference PC, I put in the runway.</p>
<p>When a CFP comes out that I want to target, I put it in the runway.
If it’s more of an aspirational target, I’ll put a question mark after
it.</p>
<p>When I have a project that I’m ready to actually start working on,
I’ll make sure key pieces and the tentative target are in the
runway.</p>
<p>When I create the section for a new term that contains deadlines I
typically target (FAccT, RecSys, increasingly SIGIR), I put those in,
even if the exact deadline isn’t available yet.</p>
<p>If something comes up that’s further out than my runway currently
extends, I create a new section for that time block (e.g. upcoming
semester or summer).</p>
</section>
<section id="using-the-runway" class="level2">
<h2>Using the Runway</h2>
<p>The result of this practice is that, in addition to the weekly and
daily plans in my <a
href="/blog/2017/02/productivity-notebook">notebook</a>, I have
continuous telemetry on where I sit with respect to my planned work for
the next year (at least).</p>
<p>When I am sitting down to plan my week (a typical Sunday evening
ritual; I’ll write more about my rituals in a <a
href="/blog/2021/02/ritual">separate post</a>), I can review the runway
to see what’s on my horizon that I need to be making progress on, and
should think about adding to the week’s Desired Outcomes.</p>
<p>When I get a new review request, I can look at the runway and see
what else I’ve already committed to during that time. I try not to be on
more than one regular conference PC in any given month.</p>
</section>
<section id="reviewing-the-runway" class="level2">
<h2>Reviewing the Runway</h2>
<p>At the beginning of each term, part of my term planning process is to
flesh out that term’s outline into per-month details, and review for
things I might be missing (as well as things I should drop or defer due
to overcommitment).</p>
<p>I also review it as needed and try to make sure it stays current
throughout the term; I re-evaluate at least once a month to make sure
the current term still reflects my goals and the term’s surprises to
date.</p>
</section>
<section id="wrapping-up" class="level2">
<h2>Wrapping Up</h2>
<figure class="autosize compact right">
<iframe src="https://giphy.com/embed/3oz8xtBx06mcZWoNJm" width="220" height="165" frameBorder="0" class="giphy-embed" allowFullScreen>
</iframe>
<figcaption class="credit">
<a href="https://giphy.com/gifs/aardman-cartoon-train-3oz8xtBx06mcZWoNJm">via
GIPHY</a>
</figcaption>
</figure>
<p>I’ve been using the runway document for a little over a year now, and
it has significantly improved my ability to keep track of my work over
time and make more realistic plans on a longer time horizon. I wish I
had started this practice years ago — work is less overwhelming when I
have it slotted in to specific times and can think ahead about what to
expect in future months and terms. I finally feel like I actually have
strategic control of my future work, in addition to the tactical control
the other pieces of my productivity strategy afford. Time and
commitments are also a more useful way to manage future planning at this
time than the Kanban-style board modeling the research pipeline.</p>
<p>The runway is a plan, not a prison. I don’t treat it as a firm
commitment to myself, and adjust it liberally as my priorities change,
new opportunities arise, and projects take more or less time than
expected. It does help make space for things, though; if a new
collaboration possibility comes up, for example, I can think more
concretely about what I might need to give up to make it work, or
communicate up front about a realistic timeline for my involvement.</p>
<p>I’m not entirely sure “runway” is really the best name for this
document, because I’m always accelerating and never taking off.
Sometimes it feels a bit more like the little dog, building his railway
track just in time to ride on it, but it’s more like he’s got the
controls for a robot that’s laying it out a few more feet in
advance.</p>
</section>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Saving Plots]]></title>
        <id>https://md.ekstrandom.net/blog/2020/09/plots</id>
        <link href="https://md.ekstrandom.net/blog/2020/09/plots"/>
        <updated>2020-09-18T04:00:00.000Z</updated>
        <content type="html"><![CDATA[<figure class="compact right autosize">
<a href="https://unsplash.com/photos/6EnTPvPPL6I"><img srcset="/images/drawing-a-chart-320.jpg 1x, /images/drawing-a-chart-480.jpg 1.5x, /images/drawing-a-chart-640.jpg 2x" src="/images/drawing-a-chart-640.jpg" alt="Drawing a chart"></a>
</figure>
<p>I use Jupyter notebooks extensively for data analysis and
exploration. It’s fantastic to be able to quickly see output, including
plots, and have it all saved and persisted and viewable on GitHub.</p>
<p>However, when it comes time to prepare for publication, I need to
save high-resolution and/or vector versions of the plots for use in
LaTeX or Word. The display in Jupyter does not have nearly high enough
resolution to copy and paste into a document and have it look acceptably
good.</p>
<p>Most of my projects, therefore, have a convenience function for plots
that are going into the paper. This function saves the plot to disk (in
both PDF and 600dpi PNG formats) and returns it so it can also be
displayed in Jupyter. That way I don’t have two copies of the plot code
— one for saving and one for interactive exploration — that can get out
of sync.</p>
<section id="python-code" class="level2">
<h2>Python Code</h2>
<p>The <code>make_plot</code> function takes care of three things:</p>
<ol type="1">
<li>Chaining the ggplot calls together (since the syntax is slightly
less friendly in Python)</li>
<li>Applying the theme I’m using for the notebook, along with additional
theme options</li>
<li>Saving the plots to both PDF and high-DPI PNG</li>
<li>Returning the plot for notebook drawing</li>
</ol>
<p>This function is built for <a
href="https://plotnine.readthedocs.io/en/stable/index.html">plotnine</a>,
a Grammar of Graphics plotting library for Python that I currently use
for most of my statistical visualization. It should be possible to write
a similar function for raw Matplotlib, or for Plotly, but I have not yet
done so.</p>
<p>It uses a global variable <code>_fig_dir</code> to decide where to
put the figures. The extra keyword arguments (<code>kwargs</code>) are
passed directly to another <code>theme</code> call, to make per-figure
theme customizations easy.</p>
<p>Code:</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> plotnine <span class="im">as</span> pn</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> make_plot(data, aes, <span class="op">*</span>args, <span class="bu">file</span><span class="op">=</span><span class="va">None</span>, height<span class="op">=</span><span class="dv">5</span>, width<span class="op">=</span><span class="dv">7</span>, theme<span class="op">=</span>theme_paper(), <span class="op">**</span>kwargs):</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>    plt <span class="op">=</span> pn.ggplot(data, aes)</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> a <span class="kw">in</span> args:</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>        plt <span class="op">=</span> plt <span class="op">+</span> a</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>    plt <span class="op">=</span> plt <span class="op">+</span> theme <span class="op">+</span> pn.theme(<span class="op">**</span>kwargs)</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> <span class="bu">file</span> <span class="kw">is</span> <span class="kw">not</span> <span class="va">None</span>:</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>        outf <span class="op">=</span> _fig_dir <span class="op">/</span> <span class="bu">file</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>        <span class="cf">if</span> outf.suffix:</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>            warnings.warn(<span class="st">&#39;file has suffix, ignoring&#39;</span>)</span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>        plt.save(outf.with_suffix(<span class="st">&#39;.pdf&#39;</span>), height<span class="op">=</span>height, width<span class="op">=</span>width)</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a>        plt.save(outf.with_suffix(<span class="st">&#39;.png&#39;</span>), height<span class="op">=</span>height, width<span class="op">=</span>width, dpi<span class="op">=</span><span class="dv">300</span>)</span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> plt</span></code></pre></div>
<p>This can be used like this:</p>
<div class="sourceCode" id="cb2"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a>make_plot(data, pn.aes(<span class="st">&#39;DataSet&#39;</span>, <span class="st">&#39;value&#39;</span>, fill<span class="op">=</span><span class="st">&#39;gender&#39;</span>),</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>          pn.geom_bar(stat<span class="op">=</span><span class="st">&#39;identity&#39;</span>),</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>          pn.scale_fill_brewer(<span class="st">&#39;qual&#39;</span>, <span class="st">&#39;Dark2&#39;</span>),</span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>          pn.labs(x<span class="op">=</span><span class="st">&#39;Data Set&#39;</span>, y<span class="op">=</span><span class="st">&#39;</span><span class="sc">% o</span><span class="st">f Books&#39;</span>, fill<span class="op">=</span><span class="st">&#39;Gender&#39;</span>),</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>          pn.scale_y_continuous(labels<span class="op">=</span>lbl_pct),</span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>          <span class="bu">file</span><span class="op">=</span><span class="st">&#39;frac-known-books&#39;</span>, width<span class="op">=</span><span class="dv">4</span>, height<span class="op">=</span><span class="fl">2.5</span>)</span></code></pre></div>
<p>The width and height are in inches.</p>
<p>And here’s <code>theme_paper</code>, a custom theme that extends
<code>theme_minimal</code> with some text cleanups:</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode python"><code class="sourceCode python"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> theme_paper(pn.theme_minimal):</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>    <span class="kw">def</span> <span class="fu">__init__</span>(<span class="va">self</span>):</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>        pn.theme_minimal.<span class="fu">__init__</span>(<span class="va">self</span>, base_family<span class="op">=</span><span class="st">&#39;Open Sans&#39;</span>)</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>        <span class="va">self</span>.add_theme(pn.theme(</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a>            axis_title<span class="op">=</span>pn.element_text(size<span class="op">=</span><span class="dv">10</span>),</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a>            axis_title_y<span class="op">=</span>pn.element_text(margin<span class="op">=</span>{<span class="st">&#39;r&#39;</span>: <span class="dv">12</span>}),</span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a>            panel_border<span class="op">=</span>pn.element_rect(color<span class="op">=</span><span class="st">&#39;gainsboro&#39;</span>, size<span class="op">=</span><span class="dv">1</span>, fill<span class="op">=</span><span class="va">None</span>)</span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a>        ), inplace<span class="op">=</span><span class="va">True</span>)</span></code></pre></div>
<p>I use these functions in the <a
href="https://github.com/BoiseState/book-author-gender/blob/master/DataSummary.ipynb">book
author gender code</a>.</p>
</section>
<section id="r-code" class="level2">
<h2>R Code</h2>
<p>I also have an R vesion from some older projects, before I switched
to Python. This one requires you to use <code>+</code> yourself; it
doesn’t have any automatic ggplot calls.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a>make_plot <span class="ot">=</span> <span class="cf">function</span>(plot, <span class="at">file=</span><span class="cn">NA</span>, <span class="at">width=</span><span class="dv">5</span>, <span class="at">height=</span><span class="dv">3</span>, ...) {</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> (<span class="sc">!</span><span class="fu">is.na</span>(file)) {</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>        <span class="fu">png</span>(<span class="fu">paste</span>(file, <span class="st">&quot;png&quot;</span>, <span class="at">sep=</span><span class="st">&quot;.&quot;</span>), <span class="at">width=</span>width, <span class="at">height=</span>height, <span class="at">units=</span><span class="st">&#39;in&#39;</span>, <span class="at">res=</span><span class="dv">600</span>, ...)</span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>        <span class="fu">print</span>(plot)</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>        <span class="fu">dev.off</span>()</span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>        <span class="fu">cairo_pdf</span>(<span class="fu">paste</span>(file, <span class="st">&quot;pdf&quot;</span>, <span class="at">sep=</span><span class="st">&quot;.&quot;</span>), <span class="at">width=</span>width, <span class="at">height=</span>height, ...)</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>        <span class="fu">print</span>(plot)</span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a>        <span class="fu">dev.off</span>()</span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a>    }</span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a>    plot</span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p>You can use it like this:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="fu">make_plot</span>(<span class="fu">ggplot</span>(frame, <span class="fu">aes</span>(<span class="at">x=</span>DataSet, <span class="at">y=</span>value, <span class="at">fill=</span>gender))</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>    <span class="sc">+</span> <span class="fu">geom_bar</span>(<span class="at">stat=</span><span class="st">&#39;identity&#39;</span>)</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>    <span class="sc">+</span> <span class="fu">scale_fill_brewer</span>(<span class="st">&#39;qual&#39;</span>, <span class="st">&#39;Dark2&#39;</span>)</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>    <span class="sc">+</span> <span class="fu">labs</span>(<span class="at">x=</span><span class="st">&#39;Data Set&#39;</span>, <span class="at">y=</span><span class="st">&#39;% of Books&#39;</span>, <span class="at">fill=</span><span class="st">&#39;Gender&#39;</span>)</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>    <span class="sc">+</span> <span class="fu">scale_y_continuous</span>(<span class="at">labels=</span>lbl_pct),</span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>    <span class="at">file=</span><span class="st">&quot;frac-known-books&quot;</span>, <span class="at">width=</span><span class="dv">4</span>, <span class="at">height=</span><span class="fl">2.5</span>)</span></code></pre></div>
<p>I also don’t have automatic theming in the R version, but it would be
easy to add.</p>
</section>
]]></content>
    </entry>
</feed>