Madison, WI
a work in progress ↙

Noah Serrault

Studying data science at UW–Madison.
Building software and making sense of data.

Skills

Pick one to highlight where it shows up below.

  • TypeScript, Next.js, React & React Native
  • PostgreSQL, MongoDB & SQL
  • Data structures & algorithms
  • Object-oriented design & testing
  • Git, Bash, Docker & Linux
  • Full-stack web & mobile apps
  • R, dplyr & Python
  • Statistical inference & regression
  • Geospatial analysis & GWR
  • Monte Carlo & bootstrapping
  • ELT pipelines & dbt
  • Visualization & data storytelling
  • Classification: kNN, Naive Bayes, trees & SVMs
  • Neural networks & deep learning
  • Clustering & PCA
  • Cross-validation & model selection
  • Gradient boosting & ensembles
  • LLMs, RAG & AI-assisted quoting

Things I’m working on

DIFY BatteryNext.js / Expo

A platform for mobile battery service: quoting, dispatch, payments, and a technician app built for work in the field.

As the sole developer, I’m building the web platform and mobile app to support preparations for DIFY’s Phoenix launch.

  • AI-assisted quoting with 12,000+ vehicle-to-battery fitment records.
  • Payment reconciliation, inventory updates, and warranty creation.
  • Encrypted offline queues that retain technician updates during network interruptions.

TypeScript · Next.js · React Native / Expo · PostgreSQL

Explore the DIFY Battery project ↗
Campus PlannerNext.js / Expo

One place for school at UW–Madison: tasks in folders, a week calendar of classes, exams and Canvas assignments, and a campus map that says when to leave for class, with live Madison Metro buses.

It started as my CS 571 (Building User Interfaces) project. I built it on my own: a website, an Android app, a Chrome extension, and a connector that lets AI assistants read the planner.

  • Imports a class schedule pasted from UW’s Course Schedule page, read by Claude Haiku.
  • Syncs Canvas files, announcements and assignments to your computer or Google Drive; assignments become tasks and get checked off when submitted.
  • Lets Claude, ChatGPT, Claude Code and Codex read your schedule and course files over MCP, read-only.

TypeScript · Next.js · React Native / Expo · Supabase · MCP

See the walkthrough →
Geospatial market analysisR / Python

Combining battery-service records with Census data to understand pricing patterns and explore potential expansion markets.

I used weighted and geographically weighted regression, spatial autocorrelation tests, and held-out markets to evaluate how well demand models transfer to new places.

See the walkthrough →

A little experience

Wisconsin Grocers Association2025

Data Analyst Intern · May–July 2025

Maintained customer records in Excel and analyzed underserved segments and potential customer bases for marketing campaigns.

DIFY Battery · Austin2021–23

Lead Technician · Self-employed

Launched the Austin market and grew monthly sales from $0 to $40K+, managing service requests, installations, scheduling, and inventory. Trained the incoming technician.

What I’m learning

University of Wisconsin–Madison

B.S. Data Science · Computer Science certificate

Relevant coursework9 courses

A closer look at the topics and coursework.

Data Science Modeling ISTAT 240
  • R & RStudio, vectors and data frames
  • Exploratory analysis, descriptive statistics and visualization
  • Data cleaning and transformation with dplyr
  • Probability, sampling and inference for means, proportions and regression
Course notes ↗
Data Science Modeling IISTAT 340
  • Probability, random variables and the central limit theorem
  • Monte Carlo simulation, estimation and hypothesis testing
  • Linear, multiple and logistic regression
  • Bootstrapping, model selection and cross-validation

Coursework: statistical analysis and reproducible reporting in R

Course materials ↗
Machine LearningSTAT 451
  • K-nearest neighbors, Naive Bayes, decision trees and linear models
  • Neural networks, support vector machines and optimization
  • Clustering, dimensionality reduction and self-supervised learning
  • Model evaluation, ensembles, link analysis and language modeling

Course project: team proposal, presentation, final report and code

Advanced Quantitative AnalysisGEOG 560
  • Statistical and spatial analysis in R
  • Multiple regression, spatial autoregression and geographically weighted regression
  • Bayesian inference, categorical prediction, SVMs and decision trees
  • Multivariate methods, principal components and time series

Coursework: five labs covering regression, spatial data, categorical prediction, SVMs/trees and multivariate methods; an independent data-analysis project

Artificial IntelligenceCS 540
  • Probability, linear algebra, PCA and natural language processing
  • Clustering, classification and regression
  • Neural networks, deep learning and generative models
  • Search, games, reinforcement learning and AI ethics

Built: PCA face compression, an n-gram text generator, hierarchical clustering of countries, gradient-descent regression, A* search, PyTorch image classifiers (Fashion-MNIST, CIFAR-100) and a fine-tuned DistilGPT-2

Course syllabus ↗
Programming IIICS 400
  • Binary search trees, rotations, red-black trees, AVL trees and B-trees
  • Graphs, minimum spanning trees and shortest paths
  • Sorting, hash tables, tries and skip lists
  • Bash, JUnit, Git, SSH, Make, code review and integration
  • Lambda expressions, HTML/CSS, JavaScript, web servers, JavaFX and regular expressions

Course projects: binary search tree and rotation implementations, red-black tree, iterator, shortest path and hash table; role-based development and two integration milestones

Programming IICS 300
  • Object-oriented design, interfaces, generics and exception handling
  • Lists, stacks, queues, heaps and binary search trees
  • Recursion, searching, sorting and complexity analysis
  • Iterators, file I/O and testing boundary conditions

Coursework: multi-class programming assignments using array-based and linked data structures

Course catalog ↗
Data Management for Data ScienceCS 574
  • Linux, Docker, relational databases, SQL joins, subqueries and window functions
  • MongoDB, geospatial queries, Elasticsearch and Kibana
  • Data ingestion, ELT pipelines, dbt, modeling and normalization
  • EDA, time series, imputation, resampling and feature engineering
  • Gradient boosting, LLaMA fine-tuning, retrieval-augmented generation and data storytelling

Scheduled projects: P1 MySQL · P2 MongoDB · P3 Elasticsearch · P4 ELT · P5 Time Series Analysis · P6 LLMs

Course repository ↗
Building User InterfacesCS 571
  • Web, mobile and conversational interfaces
  • React, React Native and event-driven interaction
  • Design thinking, visual design, accessibility and prototyping
  • Expert evaluation, usability testing and secure interfaces

Course project: a team-built interactive web application, from proposal and prototype to published site and usability testing

Also around: Agentic AI UW, dotData, cycling & triathlon.
Want to say hi? Pass me a note ✎

back to the notesNoah Serrault · projects

Projects

A visual walkthrough of things I’ve built and analyzed. No code, just what I did and what I found. Swipe or use the arrows to flip through each one.

Software

Campus Planner

CS 571 · Next.js, Expo, Supabase

One place for school at UW–Madison. I built it solo: a Next.js website, an Android app, a Chrome extension that syncs Canvas, and an MCP server so AI assistants can answer “what’s due?” from your real schedule.

full-stack webReact Native / ExpoSupabase + row-level securityChrome extensionMCP

Campus Planner task list with folders per class, grouped into Overdue, Today, Tomorrow and next week, with High priority tags.
1Tasks sit in folders, one per class, and group themselves by due date. This is the app running locally with its built-in example data.
paste the UW Course Schedule pageClaude Haiku reads the classesCanvas calendar feed adds assignmentstasks, calendar and map in one place
2Set up once from things students already have, not manual entry. Classes stop at the start of finals, and each class can get its own task folder.
Week calendar for Nov 8–14 showing class blocks for COMP SCI 540, MATH 340, COMP SCI 571 and PSYCH 202, tasks, and a red MATH 340 midterm block.
3The week view puts classes, exams and tasks together, so a midterm on Thursday night shows up next to everything else due that week.
Chrome extension or the Android appreads Canvas with your own sign-infiles to your computer or Google Driveassignments become tasks, submitted ones get checked off
4Canvas tokens are turned off at UW, so the sync runs inside your own Canvas session and the server never sees the sign-in. The phone app re-syncs every night on Wi-Fi, uploading only what’s new.
Settings → Connect an AI assistanta secret, read-only MCP linkClaude, ChatGPT, Claude Code, Codexget_schedule · get_tasks · find_course_files
5The same link works in every AI app, and making a new one turns the old one off everywhere. A local server adds the downloaded course files, PDFs included.
Getting to class page: Monday’s COMP SCI 540 card says leave home by 9:32 AM, next to a map of campus with the walking route, class building pins and 42 live Madison Metro buses.
6The map answers when to leave for class, with live Madison Metro buses on top. Here it falls back to a walking estimate; with Google Routes set up it plans the bus. Paid lookups sit behind a cache and a daily cap so the whole app runs on free tiers.
The same task list and Getting to class page on a phone, with a bottom tab bar for Tasks, Calendar and Map.
7On a phone the site switches to a bottom tab bar. The Android app (Expo) uses the same account and data.
1 / 7

what I found: a planner only works if students never keep a second list, so every import has to land in the right place on its own.

Try Campus Planner ↗

Machine learning

Fine-tuning a small GPT

CS 540

I took DistilGPT-2, a pretrained language model, trained it further on a small text dataset entirely on a laptop CPU, then studied how the sampling settings shape what it writes.

LLM fine-tuningHugging Face Transformerstokenizationtext generation

~3,000 lines of textsplit 95% train · 5% held outtokenized to 128-token chunks600 training steps on CPUfine-tuned model saved
1The pipeline. I tuned the run to finish in about 15 minutes with no GPU, using small batches, gradient accumulation and a low learning rate.
Bar chart of DistilGPT-2's top next-word guesses. With temperature 0.8, 'a' rises from 20% to 37% and 'the' from 13% to 22%.
2The model scores all 50,257 possible next tokens. A temperature of 0.8 sharpens its favorites, and top-p of 0.95 then drops the long tail, so only about 200 tokens stay in play here.
Prompt: What are Ancient Egyptian deities?“Some believe to be them, such as the ‘god of all things and gods in his heart.’ In a 2012 article on Archaeology Magazine’s blog about ancient Egypt, William L. Ross described how these two forms evolved from one another…”
3Fluent, confident, and made up. Fine-tuning taught the model the style of the data, not the facts, which is why grounding methods like retrieval matter.
CALUCIO:
Not, but you; to medore priend: my stould that
dight of mine whom to sting saly him will back;
The better never his good the unsare andwar…
4For contrast, a tiny character-level model trained from scratch learns play formatting and spelling patterns, but not real words.
1 / 4

what I found: sampling settings are a real design choice, and a fluent model is not a factual one.

Clothing image classifier

CS 540 · PyTorch

I trained neural networks to recognize 10 kinds of clothing in Fashion-MNIST, comparing a 3-layer and a 5-layer network, then dug into where they still get confused.

neural networksPyTorchtraining loopsmodel evaluation

Grid of small grayscale clothing images across ten classes: T-shirt, trouser, pullover, dress, coat, sandal, shirt, sneaker, bag and ankle boot.
160,000 training images, each 28 × 28 pixels, across 10 classes. I normalized the pixels and fed them in batches of 64.
Line chart of training accuracy over 8 epochs rising from about 80% to 92.5% for both networks.
2With an AdamW optimizer and a one-cycle learning-rate schedule, both networks pass 92% on training data and reach 89% on 10,000 images they never saw.
Confusion matrix. Most classes are above 90% correct; shirts are correct 72% of the time and often mistaken for T-shirts, pullovers or coats.
3The confusion matrix shows where it struggles. Shirts are right only 72% of the time, and 11% of them get called T-shirts. Honestly, fair.
Three test images, a sneaker, a shirt and a dress, each with the model's top three guesses. Each top guess is 99 to 100% confident and correct.
4For any image, the model gives its top three guesses with a confidence for each.
1 / 4

what I found: going deeper barely helped here (89.0% → 89.3%). The remaining errors come from classes that genuinely look alike.

Eigenfaces & a Shakespeare text generator

CS 540 · NumPy

Two from-scratch models. The first is principal component analysis (PCA) that compresses face images into a few numbers. The second is an n-gram model that writes text one character at a time.

PCAeigendecompositionlinear algebraprobabilistic language models

The average face and nine blue eigenfaces showing lighting, hair and face-shape patterns.
1From 100 faces of 3,000 pixels each, I built the covariance matrix and pulled out its top eigenvectors. These “eigenfaces” capture lighting, hair and face shape.
One face rebuilt from 5, 15, 33 and 60 numbers, getting sharper each time.
2Any face can be rebuilt as the average face plus a weighted mix of eigenfaces. More components means more detail.
Curve of variation explained: 3 components explain half, 33 components explain 90%.
3Three components already explain half the variation between faces, and 33 keep 90%. That’s about 1% of the original size.
Three bar charts of the next-letter guess after 'to be or not to b'. With more context the model grows more certain the next letter is 'e'.
4The text model counts which letter follows each short run of letters in Shakespeare. The more letters it sees, the surer it gets.

2 letters of context“En bowinw t. An ngef I re, imot nesoly hife pres selerung…”

4 letters“Is his holy be their Jack! FALSTAFF. ’Tis Pelian discorrupt…”

7 letters“From whence is nearer that didst well feel your house. Enter Dromio of Syracusian marble hand…”

5Generated text from 1.5 million characters of Shakespeare. With more context, gibberish turns into almost-Shakespeare.
1 / 5

what I found: a lot of high-dimensional data turns out to be simple underneath. A few directions describe most of it.

Clustering the world’s countries

CS 540 · Python

I grouped 167 countries by development data using hierarchical clustering that I implemented myself: it repeatedly merges the two closest groups.

unsupervised learninghierarchical clusteringfeature scalingalgorithms from scratch

167 countries × 9 indicatorschild mortality · exports · health · imports · income · inflation · life expectancy · fertility · GDP per personrescaled so each counts equallymerge the closest pair, 166 times
1The setup. Without rescaling, income (in dollars) would drown out every other measure.
Dendrogram of 22 countries. Wealthy countries merge early, and Nigeria, Qatar and Luxembourg join late.
2The merge history as a tree, shown for 22 countries. Similar countries join low down, and unusual ones join late.
Scatter of income against child mortality colored by cluster: lower income, middle income, high income, trade hubs and Nigeria.
3Cutting the tree into five groups finds income tiers, plus two outlier groups: trade hubs whose trade exceeds their whole economy, and Nigeria, with inflation over 100%.
1 / 3

what I found: the algorithm found both the obvious story (income) and the outliers that don’t fit it.

Data science

What drives battery-service prices in DFW

GEOG 560 · R, GWR

My GEOG 560 final. I joined about 16,000 DIFY service tickets with Census data for roughly 190 Dallas–Fort Worth ZIP areas to test an obvious assumption: that richer neighborhoods pay more. The charts here are a rebuild on 2024 Census data, and the main findings held.

geographically weighted regressionMoran’s I & LISAweighted regressionCensus data

Map of Dallas–Fort Worth ZIP areas showing a few significant price hot spots and cold spots, with most areas showing no significant pattern.
1Average ticket price clusters in space (Moran’s I = 0.14, p = 0.002), with a handful of clear hot and cold spots. Location matters, so a regular regression isn’t enough on its own.
Bar chart of t-statistics for eight Census factors. Only newer housing and blue-collar workforce clear the significance line. Household income does not.
2Across eight Census factors, income isn’t significant. Newer housing (newer cars with pricier AGM batteries) and a blue-collar workforce (work trucks, often with two batteries) are.
Two maps of local effects. Both newer housing and blue-collar share raise prices most on the Fort Worth side and least in central Dallas.
3Geographically weighted regression lets each effect vary by place. Both are strongest on the Fort Worth side and weakest in central Dallas. Going local lifts R² from 0.12 to 0.19.
Map of action zones: newer-housing zones in the north and southwest, and work-truck zones across northwest Tarrant and Denton counties.
4Action zones are ZIPs where an effect is both statistically reliable and in the top quarter by strength. They became ad strategies: expert AGM installs in one zone type, fast heavy-duty service in the other.
1 / 4

what I found: the obvious hypothesis was wrong. What people drive, not what they earn, sets the price.

Where to launch next: screening Phoenix

DIFY Battery · Python, R

Before DIFY expanded to Phoenix, I built a model that learns what drives demand in Austin, Dallas–Fort Worth and Houston, then scores all 127 Phoenix ZIP areas. It combines a year of completed jobs (tens of thousands of calls) with Census, workplace, local business and NOAA weather data.

leave-one-market-out validationridge regressionfeature engineeringCensus · LODES · ZBP data

Bar chart of error on a hidden market: 71% with population only, 61% adding Census vehicle data, 50% adding daytime jobs, 48% adding nearby auto businesses.
1To test it, I hid one Texas market at a time and predicted it ZIP by ZIP. Knowing where people work gave the biggest gain. Error fell from 71% to 48%, and ranking accuracy rose to 0.80.
Predicted versus actual yearly jobs for each hidden market: Austin about 1.0 times, Houston about 1.2 times, Dallas–Fort Worth about 0.6 times.
2Totals are harder than rankings. Austin landed close, Houston about 20% high, and Dallas–Fort Worth roughly a third low, so I report volume as a range, not a single number.
Map of Phoenix ZIP areas shaded by predicted demand, with 25- and 35-mile rings. The highest-demand areas run through Tempe, Chandler, Mesa, Scottsdale, central Phoenix and Peoria.
3The Phoenix screen, led by Scottsdale, Chandler and Tempe ZIPs. About 95% of predicted demand falls within 25 miles, which became the recommended pilot area.
Line chart of days at or above 95°F by month. Phoenix had 161 such days in the past year, versus 61 to 89 for the Texas markets.
4In Texas, hot stretches predict more calls over the following days, a small but consistent out-of-time gain. Phoenix had 161 days above 95°F last year, so I treat heat as an upside scenario, not a promise.
1 / 4

what I found: the model is good at ranking where to start, not at promising revenue, so the plan is a staged 25-mile pilot with clear go/no-go checks.

Where poverty clusters in the US

GEOG 560 · GeoPandas, PySAL

Using the CDC’s Social Vulnerability Index, I mapped high-poverty counties, tested whether they cluster geographically, and tried to predict them from other county data.

geospatial analysisspatial autocorrelationclassificationcross-validation

Map of US counties with 307 high-poverty counties in blue, concentrated in the Mississippi Delta, Appalachia, the Southwest and the Texas border.
1307 of 3,109 counties are flagged high-poverty. They’re not scattered: they concentrate in the Delta, Appalachia, tribal lands and the border.
Histogram of Moran's I across 999 random shuffles, clustered near zero, with the observed value of 0.33 far to the right.
2Moran’s I puts a number on it. I shuffled the map 999 times, and none came close to the real clustering (I = 0.33, p = 0.001).
Bar chart. Original logistic regression: 90% accuracy but catches 1% of high-poverty counties. Naive Bayes: 21% accuracy, catches 97%. Revisited logistic model: 81% accuracy, catches 80%.
3The accuracy trap. My first model scored 90%, which sounds great until you notice it almost never flagged a county. Revisiting it later, switching raw counts to per-person rates and balancing the classes caught 80% of high-poverty counties.
1 / 3

what I found: location matters, and accuracy alone can hide a model that’s useless for the cases you care about.

Predicting Wisconsin’s population

CS 320 · GeoPandas, SQL, rasterio

I joined census data from a SQLite database with county shapes and satellite land-cover data, then built regression models that predict population.

SQL joinsraster analysislinear regressionscikit-learn pipelines

Map of Wisconsin's 72 counties shaded by population, with Milwaukee darkest and Dane County second.
1Wisconsin’s 72 counties by population. Milwaukee and Dane stand far above the rest.
Scatter of housing units against population with a nearly perfect straight-line fit. Test R squared is 0.996, versus 0.02 for land area.
2Land area predicts almost nothing (R² = 0.02). Housing units, summed from tract-level SQL, predict population almost perfectly: R² = 0.996 on held-out counties.
Land-cover map of Dane County: mostly brown cropland, red developed areas around Madison's lakes, and green forest in the west.
3Cropping satellite land-cover data to each area’s boundary. Dane County is 47% cropland, with Madison’s development wrapped around the lakes.
Bar chart of model coefficients for each land-cover type. Developed land types have the largest positive weights.
4Across 1,542 census tracts, I counted each land-cover type and let a model weigh them. Developed land drives the prediction (cross-validated R² ≈ 0.52).
1 / 4

what I found: the right feature beats a clever model. Housing units alone explain almost everything at the county level.

House prices

Lab · scikit-learn

Predicting house prices from 545 listings, starting with a single feature and measuring how much each addition helps on homes the model hasn’t seen.

regressiontrain/test splitfeature selectionerror metrics

Scatter of lot area against price with an upward-sloping fit line. Training homes are blue, held-out test homes orange.
1I held out 20% of homes before fitting anything. Area alone gives a rough upward trend, with a lot of spread.
Bar chart of average error: 1.47 million with area only, versus 1.13 million with five features.
2Adding bedrooms, bathrooms, stories and parking cut the average error by 24% on held-out homes.
Predicted against actual price scatter. Points follow the diagonal but spread out for expensive homes.
3Predicted against actual. The model is solid mid-market but undershoots expensive homes, a sign it’s missing features like location.
1 / 3

what I found: always judge a model on data it hasn’t seen, and read the residuals, not just the score.

Lake Mendota is losing its ice

CS 540 · NumPy, PyTorch

Using 169 winters of ice records for the lake next to campus, I fit a trend two ways: an exact closed-form solution, and gradient descent written with PyTorch.

linear regressiongradient descentnormalizationextrapolation

Days of ice each winter from 1855 to 2023, trending down about 2 days per decade.
1From 1855 to 2023, the lake loses about 2 days of ice per decade: 122 days on average in the first 20 winters, 85 in the last 20.
Loss curve dropping steeply then flattening at the closed-form solution.
2Gradient descent, with the learning rate tuned by search, lands on the same answer as the exact solution within 200 steps.
Trend line extended into the future, reaching zero ice days around the year 2439.
3Taken literally, the line reaches zero ice around 2439. Climate isn’t linear, so this is a lesson in the limits of extrapolation, not a forecast.
1 / 3

what I found: the trend is real and steady. Predicting far past your data is where models quietly break.

More on the way. Pass me a note if you want the details on any of these.

pass me a note

I read every one. Email or phone, whichever you like.

a little room to scribble, too.