Part IV: Machine Learning Essentials
Chapter 17
Supervised Learning II: Classifying Rock from Logs
Get the TL;DR and the key concepts before you dive in — or as a quick review after.
Chapter 16 predicted a number, porosity, from logs. But the first question a geologist asks at a well is not "how much porosity?" It is "what is this rock?" Shale or sand? Pay or non-pay? Gas or brine? Those are categories, and getting them wrong is expensive: mislabel a sand as shale and you zone it out of the geomodel and never perforate it (an 8-ft pay sand left behind can strand tens of thousands of barrels), while a wet sand mislabelled as gas pay books reserves that were never there. Picking a category instead of a number is classification, and it is the other half of supervised learning.
The workflow is the one you already know (split, fit, predict, evaluate) but three things change, and each one carries a trap. The models change (a straight line cannot draw a boundary between four rock types). The metrics change (a single accuracy number can hide a disaster). And the failure modes change (when the rock you care about is rare, an accurate-looking model can miss almost all of it). This chapter works one problem hard enough to expose all three: assign every logged foot of an OML 58 well to one of four lithofacies, where the prize, a thin gas sand, is also the rarest and the easiest to miss.
infoWhat You'll Learn
- Frame a petroleum question as classification, and build a labelled facies dataset
- Why accuracy alone lies, and how the confusion matrix, precision, recall, and F1 tell the truth
- The rare-class problem: why a 7%-of-the-section gas pay gets missed, and how the decision threshold buys it back
- Read which logs separate which rock types, and deliver a predicted lithofacies log on a new well
lightbulbDataset Used in This Chapter
We build a self-contained, labelled facies set in the first cell: 1,500 logged samples from OML 58, each tagged with one of four lithofacies (shale, brine sandstone, gas sand, siltstone) and its four logs. As in Chapter 16 it is synthetic so every cell runs offline, but the log responses overlap the way real facies do, which is exactly what makes the rare gas sand hard.
From Numbers to Categories
In regression the target is a continuous number; in classification it is a label drawn from a fixed set of classes. Here the label is the lithofacies, the rock type a geologist would log from core or cuttings, and the features are the same four wireline logs as before. Build the set and look at what we are up against.
The class balance is the whole story in miniature. Shale and brine sandstone make up three-quarters of the section; the gas sand, the one zone an asset team actually wants to find, is barely 7%. In the field those labels would come from a core description or mud-log, and you would replace make_facies_dataset with lasio.read("well.las") for the logs joined to that facies column; everything downstream is identical. One honest caveat carries over from Chapter 16: because these logs were generated from the facies they will be used to predict, the scores here are an optimistic ceiling. Real facies overlap more, and a field model scores lower. The workflow is what transfers, not the exact number.
A First Classifier
Before reaching for an ensemble, find out how far a straight line gets you: if a linear model already separates shale from sand, the genuinely hard problem, gas from brine, is narrower than it looks. Start with the simplest model that could work, as a baseline to beat. Logistic regression is the linear model's classification cousin; it draws straight boundaries between classes and reports a probability for each class rather than a bare label. We scale the features (their ranges differ by orders of magnitude) and split with stratify=y, which keeps the same 7% gas proportion in train and test so the rare class is not lost to an unlucky split.
About 83% of logged feet classified correctly, from a straight-line model, in a handful of lines. On most dashboards that number would end the conversation. It should not, because accuracy is the metric most likely to lie to you, and the next section shows exactly how.
Beyond Accuracy: The Confusion Matrix
Accuracy is the fraction of all samples called correctly. When one class dominates, you can score high by simply ignoring the rare ones: a model that never once predicts "gas sand" still gets 93% of this section right, because gas is only 7% of it. To see what a model actually does, you need the confusion matrix (every true class against every predicted class) and the three per-class metrics that fall out of it:
- Precision: of the feet the model called gas, how many really were? (Few false alarms.)
- Recall: of the feet that really were gas, how many did it catch? (Few misses.)
- F1: the harmonic mean of the two, a single score that punishes ignoring either.
We move to a random forest here, the ensemble that won Chapter 16, because it draws non-linear boundaries and, shortly, hands us both feature importances and per-class probabilities.

The forest scores the same ~84% accuracy as the linear baseline, and the per-class report shows why that number is a trap. Shale and brine sandstone are easy (F1 ~0.9). But the gas-sand recall is about 0.52: the model finds barely half of the real gas, quietly relabelling the rest as ordinary brine sandstone. The headline accuracy never flinches, because gas is too rare to move it. In a chapter on porosity that might be tolerable. When the missed class is the pay, it is the only number that matters, and macro-averaged F1, which weights every class equally regardless of size (so the rare gas sand counts as much as the abundant shale), drops to about 0.75, honestly reflecting the two facies the model struggles with.
The Rare-Class Problem
Why is the gas sand missed? Not because the model is weak, but because gas overlaps brine sandstone in log space (same clean gamma ray, same high porosity) parting only on a softer density and neutron and a higher resistivity, and those signals are noisy. When a class is both overlapping and rare, the model's safest bet on a borderline foot is the common neighbour. Reweighting the classes barely helps, because the separating signal genuinely is not there.
The lever that does work is the decision threshold. The forest does not just vote a label; it reports a probability for each facies (the gas-sand probability is column 2 of predict_proba), and the default label is simply whichever probability is highest (so gas wins only above 0.5). But you choose that cut-off. Lower the bar for calling gas, and you catch more of it, at the cost of more false alarms. Which way to lean is an engineering decision, not a statistical one: a missed gas pay can cost a bypassed zone worth millions, while a false alarm costs one more confirmation log.

The habit worth keeping: stop accepting the default 0.5 cut-off and set the threshold by the cost of each error. On a wildcat where missing pay ends the well, you ride the curve up to high recall and accept the false alarms, then knock them down with a second log. On a field where every flagged zone triggers an expensive test, you keep the threshold high and tolerate the misses. The model gives you the probabilities; the engineering gives you the cut-off.
Which Logs Separate the Facies
A classifier you cannot interrogate is one you cannot trust on a new well. Two views earn that trust: which logs the model leans on, and where it draws its boundaries in the log space a petrophysicist already reads.

Gamma ray is the most important log, which is the geologist's instinct made quantitative: it cleanly splits clay-rich shale and siltstone from clean sand. But GR cannot tell gas sand from brine sand (they are both clean) so the model leans on density, neutron, and resistivity to make that call, the same logs a petrophysicist uses for the gas crossover, where gas suppresses both the density and neutron readings while raising resistivity, pulling those curves apart. The crossplot shows the reward and the limit at once: shale separates cleanly to the upper right, but the gas sand sits inside the lower edge of the brine-sand cloud, which is precisely why no model, and no human, classifies every foot correctly. (One caveat on the importance ranking: impurity-based importances tilt toward logs with many distinct values, like resistivity, so confirm a ranking you intend to defend with permutation importance.)
The Payoff: A Predicted Lithofacies Log
The deliverable is not a score; it is a facies column a geologist can hang next to the logs and act on. We apply the trained forest to a fresh, depth-ordered interval (a real layered sequence the model has never seen) and lay its predicted facies track beside the truth.

Read top to bottom, the predicted column reproduces the geology: shale caps and bases, two clean sandstones, a siltstone, and the gas sand at 9028-9036 ft. It is not perfect (the gas bed shows a few feet misread as plain sandstone, the same shortfall the confusion matrix quantified) but it is a continuous, defensible first-pass facies interpretation produced in milliseconds, on a well no geologist has yet described. Hand it over with the gas-sand threshold turned up, and four log curves become a ranked set of zones worth a closer look.
You have now run the supervised loop for both kinds of target: a number in Chapter 16, a category here, with the metrics and failure modes that come with each. Chapter 18 removes the part both relied on, the labels, and asks what you can still learn from logs when no one has told you the answer.
Exercises
: The Cost-Weighted Threshold
Using the trained forest's gas-sand probabilities, find the threshold that maximises gas recall while keeping precision at or above 0.60. Then suppose...
: Reading a Confusion Matrix
Train both the logistic baseline and the random forest, and print each one's confusion matrix and per-class F1. Which facies pair is most often confus...
: Does More Data Beat a Better Threshold?
Cut the training set to 25% of its rows and refit the forest; then restore the full set. Report gas-sand recall at the default 0.5 threshold for both....
: A Fifth Facies
Add a "Coal" class to make_facies_dataset (very low density ~1.6 g/cc, high resistivity, low gamma ray) at 3% abundance. Retrain and evaluate. Is coal...
Summary
- Classification predicts a category, not a number, the right frame when the petroleum question is which rock, which fluid, pay or not. The supervised loop is unchanged; the models, metrics, and failure modes are not.
- Accuracy alone lies under class imbalance. A model can score 84% while missing half the pay. The confusion matrix and per-class precision, recall, and F1 show what accuracy hides; macro-F1 weights every facies equally regardless of how rare it is.
- The rare, overlapping class is the hard one. Reweighting cannot manufacture a signal that is not there, but the decision threshold can trade precision for recall, and you set it by the cost of a miss versus a false alarm, not by default.
- Interpret before you trust: feature importances and the density-neutron crossplot show why the model calls what it calls, and where it cannot win.
- The deliverable is a facies log: a continuous, depth-registered interpretation a geologist can act on, produced in milliseconds on an un-described well.
Chapter 18 drops the labels entirely: with no core descriptions to learn from, can clustering still recover rock types, and flag the wells and sensor readings that do not belong?