Duplicate vendor payments, expense report padding, miscoded general ledger (GL) entries, and fraud may be hard to find in transaction data by line-by-line review. This analysis applies machine learning anomaly detection to your expense data, flagging the transactions that deviate most from normal patterns and giving you a ranked shortlist to investigate instead of an ocean of line items.
Worked Example (Simulated): Which Expense Rows Stand Out?
Question: Which simulated expense rows differ most from the usual pattern?
For a single numeric column, a robust z-score is a simple baseline.
Inputs: Simulated data, seed 17. The script makes 20 rows with amount, days since the prior payment, and weekday (Monday is 1). Seventeen are regular rows: amounts near 100 (kept between 65 and 135), gaps of 5 to 9 days, weekdays 1 to 5. Three rows are planted: T18 (245, 1, 7), T19 (38, 1, 1) and T20 (100, 30, 7).
Method: The script fits an Isolation Forest model with a fixed seed, so the run can be repeated. It sets contamination=0.15, which asks the model to flag the top 15 percent of rows. On 20 rows that is 3 rows, whatever the data look like. It prints the flagged rows, highest score first.
import numpy as np
from sklearn.ensemble import IsolationForest
SEED = 17
rng = np.random.default_rng(SEED)
# Simulated regular transactions: amounts near 100 and payment gaps near 7 days.
regular_amounts = np.clip(rng.normal(100, 12, size=17), 65, 135)
regular_gaps = rng.integers(5, 10, size=17)
regular_days = rng.integers(1, 6, size=17)
X = np.column_stack([regular_amounts, regular_gaps, regular_days])
# Add three simulated transactions with unusual combinations.
X = np.vstack([X, [245, 1, 7], [38, 1, 1], [100, 30, 7]])
ids = [f"T{i:02d}" for i in range(1, len(X) + 1)]
model = IsolationForest(n_estimators=200, contamination=0.15, random_state=SEED)
model.fit(X)
anomaly_scores = -model.decision_function(X)
pred = model.predict(X)
flagged = np.flatnonzero(pred == -1)
ranked = sorted(flagged, key=lambda i: anomaly_scores[i], reverse=True)
print(f"Simulated data, seed {SEED}")
print(
f"Rows: {len(X)}; features: amount, days since prior payment, "
"weekday (Monday=1)"
)
print(f"Flagged rows: {len(flagged)}")
print("ID amount gap_days weekday anomaly_score")
for i in ranked:
print(
f"{ids[i]} {X[i,0]:6.1f} {int(X[i,1]):8d} {int(X[i,2]):7d} "
f"{anomaly_scores[i]:.3f}"
)
Printed output:
Simulated data, seed 17
Rows: 20; features: amount, days since prior payment, weekday (Monday=1)
Flagged rows: 3
ID amount gap_days weekday anomaly_score
T20 100.0 30 7 0.174
T18 245.0 1 7 0.163
T19 38.0 1 1 0.121
Result: The run flags the three planted rows, in the order T20, T18, T19. The count of three comes from the setting, not from the model deciding there were three. The planted rows were built to stand apart: T19's amount of 38 is below the regular range, and T18 and T20 fall on weekday 7, which never occurs in the regular rows, so the odd weekday is part of what the model saw. This shows the method ranking rows that were built to be odd. It is not a detection rate. Check flagged rows against invoices and payment records before treating them as problems.
Limit: These rows are made up. A flag is a prompt to review a record, not proof of fraud.
Sources: Isolation Forest was published by Fei Tony Liu, Kai Ming Ting and Zhi-Hua Zhou in 2008 (IEEE International Conference on Data Mining). Isolation Forest.
The Problem With Threshold-Based Rules
Most companies that try to catch unusual expenses use simple rules: flag anything over $10,000, flag any vendor payment within 5 days of a previous payment to the same vendor, flag expense reports that exceed the monthly average by 2x. These rules catch the obvious cases: a $50,000 payment to an unknown vendor will trigger every time. But they miss the subtle ones and generate too many false positives on legitimate transactions.
To find the rows in your own data that do not fit the usual pattern, try the free anomaly detection tool.
A duplicate payment of $2,300 to a vendor who normally receives $2,300 monthly looks perfectly normal to a threshold rule. An expense report that bills $180 for dinner every Tuesday night (slightly above the $150 average but never exceeding the $200 threshold) is $30 over that average each time, and no rule catches it. A vendor who gradually increases invoice amounts by 3% per month is invisible until someone audits the full history.
Machine learning anomaly detection works differently. Instead of looking at one dimension at a time (amount, frequency, vendor), it evaluates transactions across all dimensions simultaneously. A payment might look normal on amount alone and normal on frequency alone, but the combination of that amount, to that vendor, on that day of the week, in that department, is unusual relative to the full pattern of normal transactions. The algorithm, called Isolation Forest, finds these multi-dimensional outliers that single-dimension rules miss.
How Isolation Forest Works (Without the Math)
The core idea is intuitive: unusual things are easier to describe than normal things. If you played a game of 20 questions about a transaction, a normal payment ("$2,300, to Office Depot, on the 15th, from the admin department") would take many questions to isolate from the crowd of similar transactions. But an unusual payment ("$2,300, to a vendor we have never paid before, on a Sunday, from the engineering department") can be isolated in just a few questions.
The algorithm builds many random trees. Rows that a few random splits can isolate get higher anomaly scores. Rows that take many splits to isolate, because they look like everything else, get lower scores. The scores are averaged over all the trees.
You do not need to tell the model what fraud looks like. You do not need labeled examples of "good" and "bad" transactions. You do not need to configure rules or set thresholds (beyond specifying roughly what percentage of transactions you expect to be anomalous). The algorithm learns what "normal" looks like from your data and flags everything that deviates.
What Kinds of Anomalies It Catches
- Duplicate payments: same amount to the same vendor within a short window.
- Expense report padding: an employee whose expense patterns are consistently higher than peers in the same role or department. Not a single large charge, but a pattern of slightly inflated claims.
- Ghost vendors: payments to vendors with unusual characteristics: no prior history, payments that only occur once, amounts that do not match any standard service contract.
- Miscoded general ledger (GL) entries: a transaction coded to the wrong account may look unusual because its amount, timing, or department differs from the pattern for that GL code.
- Timing anomalies: payments processed on weekends, holidays, or at unusual times of day. Legitimate payments follow business hour patterns; fraudulent ones often do not.
- Vendor frequency anomalies: a vendor that usually receives one payment per month suddenly receiving three. Or a vendor that has been dormant for six months suddenly reactivating.
The key advantage over rules is that the model catches combinations that no single rule would flag. A $900 payment is not unusual. A payment to Vendor X is not unusual. But a $900 payment to Vendor X from Department Y on a Saturday. That specific combination might score as highly anomalous because it has never occurred before.
Who This Is For
Controllers, accounts payable managers, and internal auditors at companies with $5M to $100M in revenue: large enough to process meaningful transaction volume (500+ per month) but too small for enterprise fraud detection software. Industries with significant operational spending benefit the most: construction, healthcare, professional services, manufacturing, and hospitality. Companies with distributed purchasing authority (multiple cost centers, field offices, or project-based billing) are particularly vulnerable because no single person sees all transactions.
A common alternative is periodic manual sampling: the controller pulls a random sample of transactions each month and reviews them. This analysis can instead rank transactions for review. Check flagged rows against invoices, payment records, and known outcomes. This page does not show that the method finds more problems than random sampling.
What Data You Need
A CSV export from your general ledger, accounts payable system, or expense management platform (QuickBooks, Xero, Expensify, Concur, Brex). You need at least two numeric columns that characterize each transaction:
- Transaction amount: the dollar value of each payment or expense
- Vendor payment frequency: how often this vendor appears in your data (can be computed from the raw data before upload)
Additional numeric features that significantly improve detection:
- Days since last payment to this vendor: captures unusual timing patterns
- GL code average deviation: how far this transaction's amount deviates from the historical average for its GL code
- Department budget utilization: what percentage of the department's monthly budget this transaction represents
- Day of week: encoded as 1-7, captures weekend processing anomalies
The model accepts 2-20 numeric features. More dimensions give it more ways to distinguish anomalies from normal transactions. Categorical columns like vendor name or GL code should be excluded from the feature mapping. The model requires numeric inputs only. Use those columns for investigation after the anomalies are flagged.
More rows give the model a clearer picture of what is normal. The example above uses 20 made-up rows only to show the steps.
How to Read the Report
Anomaly Score Distribution: a histogram showing scores across all transactions. Normal transactions cluster at low scores; anomalies sit in the right tail. A gap between score groups is a pattern in the results, not proof that the model is right. Check flagged transactions against records and known outcomes. A gradual tail with no clear gap means the boundary is fuzzy and you should interpret borderline cases with caution.
Top Anomalies Table: this is your action list. The 20-50 most anomalous transactions ranked by score, with all feature values shown. For each transaction, you can see exactly why it was flagged: unusual amount, unusual timing, unusual vendor frequency, or some combination. Start your investigation here.
Feature Importance, which dimensions drove the anomaly scores most. If "transaction_amount" dominates, your anomalies are primarily unusually large or small payments. If multiple features contribute roughly equally, the anomalies are multi-dimensional. They look unusual across several characteristics simultaneously, which often indicates more sophisticated patterns.
Normal vs. Anomaly Comparison: side-by-side statistics showing how anomalous transactions differ from normal ones. Maybe anomalous transactions are 10x the average amount. Maybe they have normal amounts but come from vendors with unusually low payment frequency. This comparison table makes the differences concrete.
Building a Review Workflow
Monthly Cadence
Run the analysis at month-end on the full month's GL export. Review the top 20 anomalies. Most will have innocent explanations: a legitimate one-time purchase, a vendor who changed billing frequency, a correctly coded but unusual project expense. Flag the ones that do not have obvious explanations for deeper investigation.
Prioritize by Score
Anomaly scores are continuous, not binary. Scores are tool specific. Use the report's score definition and ranking, then check flagged rows against records and known outcomes. Start with the highest scores and work down. You will quickly develop a sense for what score level separates "worth investigating" from "probably fine" in your specific data.
Track False Positives
Keep a log of which flagged transactions turned out to be legitimate. Over time, this helps you tune the contamination parameter, if the model flags 5% of transactions but only 1% are actually problematic, reduce the contamination setting from 5 to 2. This narrows the investigation list and reduces reviewer fatigue.
When to Use Something Else
- Want to profile your expense data before running anomaly detection: Use the Data Explorer to understand distributions, identify which columns to use as features, and spot obvious data quality issues.
- Need to compare expense levels across departments: Use ANOVA to test whether spending differs significantly across cost centers.
- Want to forecast future expenses: Use revenue forecasting applied to expense data to project monthly burn rate.
- Only tracking one metric (e.g., total daily spend): A simple Z-score approach is easier and more interpretable for univariate anomaly detection.
Your turn
Bring your own data and the question you actually need answered.
MCP AnalyticsData science by email Send us your data and your question, and we will reply. ds@mcpanalytics.ai