Fin Maverick
Foundations VocabularyAccounting & ReportingEconomics & MacroQuant Methods & ProgrammingBusiness & Company AnalysisCorporate Finance & ValuationBehavioural Finance
Banking & Market InfrastructureFixed Income & RatesDerivatives & Structured ProductsPublic EquitiesTransactions & DealsPortfolio ConstructionFunds & AMCs
Private Markets & AlternativesRisk, Treasury & ControlAI & Digital FinanceStochastic Calculus & PricingWealth & Personal FinanceIndian Markets & RegulationProfessional Practice
CalculatorComparison
Frameworks
Explore Bootcamps
Equity ResearchPortfolio ManagementMutual Fund MasteryFinancial LiteracyInvestment Banking Analyst
Private Equity AnalystHedge Funds AnalystBreaking Into VCBreaking Into QuantsAI For Finance
Financial Analyst ProgramRisk Management ProgramPrivate Wealth ManagementDebt Capital MarketsDerivatives Foundation
Explore Internships
Equity Research InternMutual Fund Intern
Portfolio Management InternFinancial Literacy Intern
Explore Micro Courses

Equity Research6

Writing an Investment ThesisBuilding a Discounted Cash FlowReading an Annual Report FastReading a Sector Before a CompanySpotting Quality of Earnings Red FlagsBuilding a Revenue Forecast From Drivers

Portfolio Management3

Rebalancing: When, Why and What It CostsStrategic and Tactical Asset AllocationMeasuring Risk in a Portfolio

Mutual Fund Mastery3

Comparing Funds Without Being FooledHow a NAV Is Struck and Which Day You GetReading a Fund Factsheet Properly

Derivatives Unlocked4

Hedging a Real ExposureThe Greeks, PracticallyFutures, the Basis and What Moves ItReading an Option Payoff

AI For Finance2

Retrieval and Grounding for FinanceDocument Extraction in Finance

Breaking Into Quants4

Backtesting a StrategyHypothesis TestingCleaning Financial DataRegression for Finance

Breaking Into VC3

Sizing a MarketReading a Term Sheet as a FounderHow a Venture Round Actually Works

Financial Analyst Program4

Common Size and Trend AnalysisReading a Cash Flow StatementRatio Analysis That Says SomethingBuilding a Working Capital Schedule

Risk Management Program2

Credit Exposure and How It Is ReducedValue at Risk and What It Hides

Investment Banking Analyst3

Precedent Transactions and Why They DifferReading a Term Sheet StructurallyBuilding a Comparable Companies Table

Private Wealth Management3

Tax Aware Portfolio DecisionsBuilding a Client Risk ProfileGoal Based Planning Arithmetic

Debt Capital Markets3

Analysing an Issuer's CreditDuration and What It Does Not Tell YouBond Pricing and Yield Mechanics

Private Equity Analyst2

Fund Waterfalls and CarryThe LBO in Structure

Hedge Funds Analyst2

Short Selling MechanicsLong Short Mechanics
Courses
Explore Career Roadmaps
Investment Banking AnalystEquity Research AnalystVC AnalystPrivate Equity AnalystHedge Funds Analyst
Quant AnalystAI For FinanceFinancial Analyst ProgramPrivate Wealth ManagementDebt Capital Markets
Risk Management ProgramDerivatives FoundationPortfolio ManagementMutual Fund Mastery
PartnershipsShowdown
Log inSign up
Quant Analyst · CoreTrack
1Quantitative Methods, Financial Data & Programming
iProbability
Probability in FinanceRandom VariableProbability DistributionsThe Normal DistributionNormal Distribution ProbabilityThe Lognormal DistributionRandomness vs Uncertainty
iiStatistics and Inference
Population and SampleMean, Median and ModePrecision and AccuracyVariable TypesVariance, Standard Deviation and…Dispersion MeasuresStatistical BiasEffect SizeHypothesis TestingThe Sampling DistributionSkewnessKurtosisCovarianceConfidence IntervalArithmetic Mean vs Geometric MeanStatistical Significance vs Economic…Confidence Interval vs Prediction IntervalHow to Summarise a…
iiiCorrelation and Regression
RegressionCorrelation and CausationOrdinary Least SquaresInteraction TermsRegression CoefficientsRegression vs ClassificationHow to Build a…Spurious CorrelationRegression, Correlation and FitResidualsMulticollinearityAutocorrelation and Partial Autocorrelation
ivTime Series
Time Series in FinanceSimple, Weighted and Exponential…Moving Average CalculatorPrice, Return and Level SeriesHow to Prepare Time-Series…LagFrequencySeasonalityTimestampsTrendStationarity and the Unit RootHeteroskedasticityLeadRolling WindowsDifferencing
vSimulation and Numerical Methods
SimulationMonte Carlo SimulationHow to Run a…Numerical MethodsIterationResampling and the BootstrapPseudorandom Numbers and the SeedConvergence and ToleranceNumerical Stability
viOptimisation
OptimisationLocal and Global OptimaConstraintsConvex OptimisationThe SolverLinear ProgrammingThe Objective FunctionConstraint ViolationThe Feasible SetLagrange MultipliersQuadratic Programming
viiModelling Practice
Linear, Logistic, Ridge and…Training, Validation and Test…The ModelModel ErrorDependent and Independent VariablesThe ROC Curve and AUCWhat a Model HoldsMSE, RMSE, MAE and MAPEPrecision and RecallCross Validation and RegularisationOverfitting and UnderfittingReturn Series MeasuresSimple, Compound and Log Return
viiiBacktesting and Research Integrity
BacktestingBacktest vs Live PerformanceHow to Document a…How to Prevent Backtest…Out-of-Sample TestingWalk-Forward AnalysisMultiple TestingP-HackingData Snooping
ixData Quality and Structure
Data QualityThe DatasetSelection and Survivorship BiasVersioned DatasetsData Structures in FinanceData CleaningMissing Data and Null ValuesStructured Data vs Unstructured DataMissing Data vs ZeroData Validation vs Data CleaningOutliersDuplicate Records
xProgramming for Finance
Data PipelinesAPIs for Financial DataAPI vs CSV FileDatabases in FinancePython for FinanceJoinsSQL for FinanceThe Analysis Workflow
xiQuantitative Research
Research DesignThe Data Generating ProcessReproducibilityPeer Review in Analytical WorkThe Research HypothesisRobustness and Sensitivity
2Stochastic Calculus & Derivative Pricing Theory
iProbability Foundations
The Probability SpaceRandom VectorsSigma-AlgebraExpectationSample Space and EventsDensity and Distribution FunctionsRisk-Neutral ProbabilityState Price Density vs…
iiStochastic Processes and Jumps
Properties of a Stochastic ProcessMartingaleBrownian Motion and Its PropertiesBrownian Motion vs Geometric…Stopping TimeThe Markov PropertyState VariablesTransition ProbabilityQuadratic VariationQuadratic Variation vs Ordinary…Submartingale and SupermartingaleMartingale RepresentationMarkov Process vs MartingaleOptional StoppingFiltrationJump ProcessesThe Poisson ProcessLevy ProcessesJump Diffusion
iiiIto Calculus
The Ito IntegralThe Ito Integral vs the Riemann IntegralInfinitesimals in Stochastic CalculusQuadratic CovariationIto's LemmaHow to Apply Ito's…The Infinitesimal GeneratorIto Calculus vs Ordinary Calculus
ivStochastic Differential Equations
Stochastic Differential EquationsStochastic Differential Equation vs…Drift and DiffusionStrong and Weak Solutions ComparedDiscretisationGeometric Brownian Motion
vPricing Theory and No-Arbitrage
No-ArbitrageGirsanov, Radon-Nikodym and Change…Physical and Risk-Neutral Measures…The Fundamental Theorems of…The Law of One PriceThe Pricing KernelDiscount Factors and Zero-Coupon PricesReplication vs HedgingComplete Market vs Incomplete MarketClearing Margin Architecture
viOption Pricing Theory
European and American OptionsMonte Carlo European OptionThe Black-Scholes PDEBlack Scholes and the GreeksThe Payoff FunctionThe Binomial ModelBinomial Option PricingDelta Hedging in TheoryBoundary, Initial and Terminal ConditionsThe Exercise BoundaryHow to Check Put-Call…
viiVolatility Models
Constant, Local and Stochastic…Vasicek Model vs CIR ModelThe Heston ModelThe SABR ModelThe Volatility ProcessImplied VolatilityVolatility Smile vs Skew vs Surface
viiiInterest Rate Models
Interest-Rate DerivativesMean ReversionThe Zero-Coupon BondThe Ornstein-Uhlenbeck ProcessThe Discount CurveZero RatesShort-Rate Model vs Market Model
ixNumerical Pricing
Closed Form and Numerical…Monte Carlo PricingEuler and Milstein Schemes ComparedTree MethodsFinite Difference MethodsNumerical Error and StabilityVariance Reduction
xCalibration and Model Risk
Model OverrideMarket Price and Model PriceCalibrationHow to Document a Pricing ModelThe Educational Illustration LabelMarket ConventionsModel Uncertainty and LimitationsBacktesting a Pricing ModelIdentifiabilityCalibrated ParametersThe Calibration Loss Function

Joins: Combining Two Datasets Without Corrupting Either

A join sets two tables side by side wherever a shared column matches. Join the 32 Neelbagh return rows to the day book on the stall alone and 50 rows come out, with the takings total overstated by Rs 11,66,200/-. Join on the stall and the month instead and 13 come out. Nothing reports an error either time, so count the rows yourself.

Something is missing from both of those joins. No warning appeared. No column turned red. No script stopped early. Both joins ran cleanly, returned a wider table than the one that went in, and handed back something that looked exactly like the sort of thing being hoped for. The damage a join does is invisible in the table it produces and visible only in a count that has to be taken deliberately.

Here is the everyday shape of it, before any of the rupees. Twelve caterers work out of one wedding hall, and every month each of them sends the hall office a single billing sheet. The hall office also keeps a running notebook for anything worth remembering: a kitchen closed for repair, a room lent out for an afternoon, a bill that arrived twice. Now somebody asks for the billing and the notebook on one page. The clerk pairs each billing sheet with whatever the notebook holds under that caterer's name. A caterer with three entries in the notebook has its billing sheet written out three times, and its month of billing is now lying in the pile in triplicate. Nobody typed a wrong number. Nobody double counted on purpose. Copying a row copies the money on it, so the total simply grew.

What is already on the record, and what is added to it?

Three pieces of groundwork arrive already done. The first is the Neelbagh market itself, an invented covered market whose office keeps one row per stall per month over four months, and the file it hands over holding 32 rows and eight columns with all the faults it has always had. The second is the named conventionA counting rule written down before the work starts. A second person repeating it lands on the same figure rather than on some other perfectly defensible number. for the average takings per stall monthOne stall in one particular month, treated as a single thing to be counted. The unit the market office files by, so it is the unit anything joined to that file has to line up with.. The convention totals every takings cell that carries a figure worth using, then divides by the count of those cells and by nothing else. A box left blank holds no figure. Neither does the office codeA value a record keeper puts into a number column to carry a message rather than a quantity, so a reader who takes it literally adds the message to a total. the market writes when a return never arrived. A true zero, though, is a figure and goes in. The third piece is the discipline of noting how many rows there were before an operation and how many there are after. Nothing so far has leaned on that count as heavily as a join does.

The market office also keeps a day book: a running log of twelve notes, one row per note, carrying the stall, the stall name as the office wrote it, the month, the day it was written down and the wording of the note. Neither table changes. What changes is what happens when somebody puts them side by side. Both tables are correct. Both were kept properly. The trouble is manufactured entirely at the moment of joining, by a decision that takes half a second and is almost never written down.

Both tables are printed below in full. Every count below was worked out from these rows, and any of them can be redone with a pencil.

The returns table as the market office hands it over. Thirty two rows, and 31 of the takings cells carry a number.
StallName as writtenMonthTakingsFiled on day
NB-01Kadamba Idli142,0006
NB-02Chandan Tea131,0006
NB-03Harit Greens138,0006
NB-04Peetal Utensils126,0006
NB-05Bansi Flour155,0006
NB-06Ilaka Fruit134,0006
NB-07Sundari Chaat140,0006
NB-08Peeli Mithai142,0006
NB-01Kadamba Idli244,0007
NB-02Chandan Tea233,0007
NB-03Harit Greens236,4005
NB-03Harit Greens239,70019
NB-04Peetal Utensils207
NB-05Bansi Flour2(empty)7
NB-06Ilaka Fruit236,0007
NB-07Sundari Chaat241,0007
NB-08Peeli Mithai245,0007
NB-01Kadamba Idli343,0008
NB-02Chandan Tea332,0008
NB-03Harit Green341,0008
NB-05Bansi Flour354,0008
NB-06Ilaka Fruit335,0008
NB-07Sundari Chaat34,80,0008
NB-08Peeli Mithai34,80,0008
NB-01Kadamba Idli445,0006
NB-02Chandan Tea499,9996
NB-03Harit Green440,0006
NB-04Peetal Utensils428,0006
NB-05Bansi Flour453,0006
NB-06Ilaka Fruit437,0006
NB-07Sundari Chaat442,0006
NB-08Peeli Mithai447,0006
32 rowsNine spellings for eight stallsFour monthsRs 21,40,099/-31 figures
The day book. Twelve notes covering eight stalls and eleven stall months, with the shaded pair sitting on one stall month.
StallName as writtenMonthNote dayThe note
NB-01Kadamba Idli14pitch reopened after repairs
NB-02Chandan Tea46no return received
NB-03Harit Greens25return filed
NB-03Harit Greens219return revised and the first figure withdrawn
NB-03Harit Greens38name written short on the return
NB-04Peetal Utensils38stall shut for the month
NB-05Bansi Flour27takings box left empty on the return
NB-05Bansi Flour39pitch measured again
NB-06Ilaka Fruit16pitch shared with a visiting trader
NB-07Sundari Chaat312wedding order taken for a school hall
NB-08Peeli Mithai39return slip reads Rs 48,000/-
NB-08Peeli Mithai46licence copy taken
12 notesEight spellings for eight stalls11 stall monthsOne pairOne stall month carries two
Breaking Into Quants Bootcamp — Fin Maverick

What does a join actually do, and what is the only question worth asking?

A join takes each row on the left, looks along the right hand table for every row whose shared column matches it, and writes out one output row for each match it finds. The search and the copy are the whole operation. Everything difficult about joining hides in the words "for each match it finds". One match produces one row. Two matches produce two rows. No matches produce nothing at all, unless the join has been asked to keep the left row anyway.

The output row count is not the input row count and was never going to be. A join is not a merge of two things into one thing of the same size. A join is a search, run once per left row, and its result is however many hits that search returned. So the only question worth asking first, before a single figure in the result is looked at, is how many rows went in and how many came out. Every other question about a join is a refinement of that one.

A second thing costs nothing to fix in place now and saves an argument later. The order the output rows come back in is a fact about how the join was carried out, not a fact about either table. If a stall appears first in the result it is because that is where the search happened to reach it. Nothing may be read off row orderWhere a row happens to land in the sequence. The position records how the table was put together and carries no meaning at all about the thing the row describes. in a joined table, not a ranking, not a recency, not a first and last. This matters here because a joined table often looks sorted, and a table that looks sorted invites people to read meaning into the top of it.

Try it out

A join has just run and produced a wider table. Before any figure in it is looked at, what is the one question to ask?

What does one row of each table describe?

Stating it as a full sentence for each table is the single habit that prevents everything below. One row of the returns table describes one stall in one month. A row of the day book describes a single note and nothing else. The two sentences are not the same shape, and every difficulty below comes from the difference.

Work through the counts. The returns table holds 32 rows over four months. The day book holds 12 notes. The 12 notes cover eight stalls, and they cover 11 stall months. Twelve notes across 11 stall months means one stall month carries two notes, and it does: NB-03 in month 2, one note written on day 5 and one on day 19. Those three counts are the entire arithmetic of the day book, and they are enough to predict most of what follows.

Neither of those facts is a fault in either table. Two things happened to NB-03 in month 2: a return was filed and then it was revised. The market office was right to write two notes. A log is a log, so the office was equally right to keep the day book at one row per note rather than one row per stall month. Both tables are correct records of what they record. GrainWhat one row of a table describes, stated as a sentence rather than assumed. Two tables can share every column name and still be counting different things. is not a quality problem to be fixed; it is a property to be known before it is acted on.

TWO TABLES, TWO GRAINS, AND THEY ARE NOT THE SAME SHAPE THE RETURNS TABLE NB-03 month 2 Rs 36,400/- day 5 NB-03 month 2 Rs 39,700/- day 19 NB-03 month 3 Rs 41,000/- day 8 NB-05 month 2 (empty) day 7 and 28 further rows 32 rows THE DAY BOOK NB-03 month 2 day 5 return filed NB-03 month 2 day 19 return revised NB-03 month 3 day 8 name short NB-05 month 2 day 7 box left empty and 8 further notes 12 notes THE GRAIN, AS A SENTENCE One row describes one stall in one month. THE GRAIN, AS A SENTENCE One row describes one note. Eight stalls, 11 stall months, 12 notes. Twelve across eleven means one stall month carries two, and it is NB-03 month 2, shaded above. Neither table is wrong. They simply count different things.
In the returns table a row is one stall's single month, while in the day book a row is one note, so 12 notes spread over 11 stall months leaves exactly one stall month, NB-03 month 2, holding two of them.
Try it out

The day book covers eight stalls across 11 stall months in 12 notes. Which of those three numbers shows that a join on the stall and the month will multiply rows, and how?

AI For Finance Bootcamp — Fin Maverick

Where does the damage start, if it is not the join?

Set the two grain sentences beside each other and the rule falls out on its own. If the shared column is unique on the right hand table, then every left row finds at most one match, every left row is written out at most once, and the count survives the operation. If the shared column is not unique on the right, then a left row matching twice is written out twice, a left row matching three times is written out three times, and the money on that row is copied along with it.

Take NB-08 month 1, a plain row with Rs 42,000/- in it and nothing unusual about it at all. Join on the stall alone. The day book holds two notes for NB-08, one about month 3 and one about month 4. Neither has anything to do with month 1. The join does not know that and was never asked to know it: the shared column said NB-08, both notes say NB-08, so the month 1 return comes out twice. Rs 42,000/- is now sitting in the result as Rs 84,000/-, and every rupee of the second one was manufactured by the copy.

So where did the damage start? Not in the join. It did precisely what it was told, and it reported success because it succeeded. Not in either table, both of which are faithful records. The damage started at the moment somebody chose a column to join on without first writing down what one row of the right hand table describes. The choice took no time, produced no artefact, and left no trace in the output. Choosing a join column is the cheapest mistake to make and the most expensive one to find.

ONE ROW, TWO MATCHES, TWO ROWS OUT, AND THE MONEY COPIED WITH THEM THE LEFT ROW NB-08 month 1 Rs 42,000/- MATCHING ON THE STALL NB-08 month 3 return slip note NB-08 month 4 licence copy note WHAT COMES OUT NB-08 m1 + month 3 note Rs 42,000/- NB-08 m1 + month 4 note Rs 42,000/- The stall column is not unique in the day book, so one return matching twice is written out twice. One month of takings worth Rs 42,000/- now totals Rs 84,000/-, and no error was reported anywhere.
A left row that matches twice is written out twice and its takings figure is counted twice with it, so one Rs 42,000/- month arrives in the result as Rs 84,000/- without anything going wrong.

How much does the wrong column actually cost, in rupees?

Stop treating this as a caution and price it. Join all 32 return rows to the day book on the stall alone, keeping every match. Thirty two rows go in and 50 rows come out. The count of rows is the first number, and it is free. Anybody could have had it before computing anything at all.

Now the money. The one empty box on the record has been copied twice along with everything else, so of those 50 output rows, 48 carry a usable takings figure. The takings total across them reads Rs 33,06,299/- against the Rs 21,40,099/- that went in. The overstatement is Rs 11,66,200/-, or 54.4928 per cent of the true total. A report of the month's takings built on that table is not slightly wrong. The report is more than half as big again as the record it claims to describe.

Here is the part that catches people. Apply the named convention to the same corrupted table. The average takings per stall month comes out at Rs 68,881.23/- against a true Rs 69,035.45/-. The average has moved down by Rs 154.22/-, or 0.2234 per cent. Read those two figures together: the same single operation, on the same table, at the same moment, overstates the sum by 54.4928 per cent and moves the average by roughly a fifth of one per cent in the other direction.

Why does that happen? Because a sum accumulates every duplicate while an average divides by a denominatorThe count sitting underneath an average. Change the count and the same total starts answering a different question. The count belongs in the same sentence as any figure it produced. that grew at almost the same rate. Duplicating rows inflates the top and the bottom of the fraction together, so the ratio barely stirs. A sum breaks loudly and an average hides the same damage. A report built on averages can run corrupted for months without ever looking wrong.

ONE JOIN, TWO REPORTS, AND ONLY ONE OF THEM MOVES HOW FAR EACH ONE MOVED, ONE SHARED SCALE 0 20 40 60 per cent up 54.4928 THE TOTAL Rs 21,40,099/- to Rs 33,06,299/- down 0.2234 THE AVERAGE Rs 69,035.45/- to Rs 68,881.23/- drawn at its true size here THE SAME AVERAGE, ON A SCALE THAT SHOWS IT Rs 69,035.45/- what the record says Rs 68,881.23/- what the joined table says down Rs 154.22/- This panel runs from Rs 68,800/- to Rs 69,100/- rather than from nothing, which is the only way a move this small becomes visible at all. A reader watching the average sees nothing.
The same join overstates the takings total by Rs 11,66,200/-, which is up 54.4928 per cent, while moving the average down by only Rs 154.22/-, which is 0.2234 per cent, so a sum breaks loudly and an average hides identical damage.
Try it out

The same join overstates the total by 54.4928 per cent and moves the average by 0.2234 per cent. Which of the two reports would have been caught within a day, and why is the other one worse?

Try it out

Joining the same 32 rows on the stall and the month, keeping only matches, turns 32 rows into 13. How many rows were dropped, and do the two numbers add up?

Risk Management Program Bootcamp — Fin Maverick

Can one join drop rows and manufacture rows at the same time?

A join can do both, and this one does. Join the 32 return rows to the day book on the stall and the month together, keeping only the rows that match. Thirty two rows go in and 13 come out. The net is 19 rows fewer, and that net is the only number anything will show.

The net is a lie. Break it open. Of the 32 return rows, 11 found a note for that exact stall month and 21 found none at all, so 21 rows were dropped outright, taking their takings with them. Meanwhile NB-03 month 2 sits in the returns table as two rows, one for Rs 36,400/- filed on day 5 and one for Rs 39,700/- filed on day 19, and that same stall month carries two notes in the day book. Two returns meeting two notes produce four output rows where two rows went in. Two of those four were manufactured by the join.

So the true account is 21 rows dropped and 2 rows manufactured, and 21 less 2 is 19. No part of the system reports either the 21 or the 2, and the net figure of 19 reports neither of them. Worse, 19 is a plausible looking number. Somebody who counted before and after, saw 32 become 13, and reasoned that 19 rows had no matching note would be wrong about 19 separate rows and would have no way of discovering it from anything on the screen. A net row count that happens to look explainable is the most dangerous reading of all.

THE NET IS 19, AND 19 IS NOT WHAT HAPPENED 32 rows go in 19 fewer and that is all anything reports 13 rows come out split it open and there are two separate events 21 ROWS DROPPED, FINDING NO NOTE AT ALL Real months of real trading, gone from the result because no note happened to be written about them that month. 2 ROWS MANUFACTURED BY THE JOIN ITSELF NB-03 month 2 is two return rows, and that stall month carries two notes. Two meeting two gives four rows where two went in. 21 dropped less 2 manufactured is 19, and 19 describes neither of them. Nothing in the operation reports the 21 or the 2. Both had to be counted by hand, from the two tables above.
A join that turns 32 rows into 13 dropped 21 rows and manufactured 2 in the same operation, and the net count of 19 rows fewer reports neither of those two numbers.

What happens when the join uses the readable column instead?

Every table here carries a stall name as well as a stall code, and the name is the pleasant one to work with. NB-03 means nothing to anybody; Harit Greens is a shop that can be pictured. So joining on the name feels natural, and it is the mistake this record was built to punish.

The market office wrote the stall name into the returns table on every row it appears in, four separate typings for a stall that traded all four months. On NB-03 it typed Harit Greens in months 1 and 2 and Harit Green, without the final letter, in months 3 and 4. The day book always wrote the full spelling. So the returns table carries nine distinct names for eight stalls and the day book carries eight, and that gap of one is a spelling rather than a stall.

Join the two tables on the name and those two short spelled rows match nothing. NB-03 month 3 worth Rs 41,000/- and NB-03 month 4 worth Rs 40,000/-, Rs 81,000/- between them, disappear from the result. Neither row is damaged or doubtful. Both are real trading months of a stall that traded every month of the record, and both are simply not in the answer any more. Nothing is reported. From the join's point of view nothing went wrong. It searched for Harit Green, found no such thing, and moved on.

The name column looked like the obvious one to use precisely because it was readable, and readable means retyped, and retyped means eventually inconsistent. A stall code is chosen as the joining column for the opposite reason. An identifierA label that exists purely to point at one thing. An identifier has no size, stands for no quantity, and gives nobody a reason to reword it. is dull on purpose, is copied rather than composed, and carries no version anybody feels tempted to improve.

ONE MISSING LETTER, TWO REAL MONTHS, Rs 81,000/- GONE IN THE RETURNS TABLE NB-03 month 3 Harit Green Rs 41,000/- NB-03 month 4 Harit Green Rs 40,000/- NB-03 months 1 and 2 Harit Greens, and these match no match IN THE DAY BOOK, EVERY TIME Harit Greens The office wrote the full spelling on all three of its NB-03 notes, so there is nothing here for a short name to find. Nine spellings in the returns table, eight in the day book. The gap is one letter. Rs 41,000/- and Rs 40,000/-, which is Rs 81,000/- of real takings from a stall that traded all four months, leave the result and nothing anywhere says so. The stall code would have matched all four rows without effort.
Joining on the name loses NB-03 month 3 and month 4, worth Rs 41,000/- and Rs 40,000/-, because the returns table spells that stall two ways while the day book spells it one way throughout.
Try it out

Joining on the stall name quietly loses Rs 81,000/- of real takings. Which column should have carried the join instead, and what property of that column is the actual reason?

Try it out

The panel underneath moves the join column through five settings, keeping every left row each time. Does the count of rows coming out only ever rise, only ever fall, or both?

Play with it

Move the join column through five settings and watch the stack cross the line.

One control moves: the column or columns the join is made on. Every setting keeps every left row, so the join type never changes and only the key does. The two tables never change either. The heavy rule marks the 32 rows that went in, cells past it are rows the join added, and a filled cell is an output row that actually carries a note. Switch to the grid view to see the same setting laid out as eight stalls by four months. The grid shows two settings agreeing on the count while disagreeing on the work. At the opening setting the join is on the stall alone, 32 rows go in and 50 come out, all 50 carry a note, and the takings total reads Rs 33,06,299/- against the Rs 21,40,099/- that went in.

Jump to a setting:
Draw it as:
Rows out of 32 in
50
Of those, carrying a note
50
The takings total
Rs 33,06,299/-
Move on the total
up Rs 11,66,200/-

Educational illustration. The returns table is the file exactly as it was handed across, 32 rows, and the day book is its 12 notes, both set out in full further up. Any count in this panel can be redone by hand. Every setting keeps every left row, and the stack can never sink below the heavy rule. The counting rule admits only cells holding a usable figure, so the single blank box rides through each join without ever reaching the arithmetic. Averages are held as exact quantities and rounded a single time, at the moment of display.

Investment Banking Analyst Bootcamp — Fin Maverick Building a Comparable Companies Table — free micro-course from Fin Maverick

Is a left join a safe join?

Most readers arrive having been told to keep every left row and stop worrying. Keeping every left row does solve exactly one problem: a left row that finds no match is written out once, with the right hand columns left empty, instead of vanishing. Keeping every left row is genuinely useful, and it turned 21 dropped rows into 21 kept rows in the panel above. A left row that finds three matches is still written out three times, so keeping every left row does nothing about multiplication.

Here is the ladder. The same 32 rows, keeping every left row every time, through the five keys:

Rows out of 32 going in, keeping every left row at every setting. Only the last setting returns the record it was given.
The column the join is made onRows outCarrying a noteWhat that means
The stall alone5050Eighteen rows more than went in, every one of them a copy
The stall name alone4644Fourteen more than went in, and two short spelled rows carry nothing
The stall and the month3413Two more than went in, from the one stall month with two notes
The stall name and the month3412The same count as the row above, and not the same rows
The stall and the month, day book collapsed first3211What went in, comes out

Only the last setting returns what it was given. Four of the five hand back a table larger than the record, and a reader who was told that keeping every left row makes a join safe has four different corrupted tables to choose from.

Now the trap inside the trap, and it is the reason a row count on its own is not enough. Look at the third and fourth rows of that ladder. Both return 34 rows. The two results are not the same 34. Joining on the stall and the month attaches a note to 13 of those rows. Joining on the stall name and the month attaches a note to 12. The row that lost its note is NB-03 month 3: the day book wrote a note for that exact stall month, the note is sitting in the table above, and the join on the name silently returned that row with an empty note because the returns table spells the stall short in month 3.

The row count caught nothing there, inside the very example used here to argue for row counts. Row counting has an honest limit, and this is it. So the count of rows carrying a match is the second check, and it costs one more line: rows in, rows out, and of the rows out, how many actually found something on the right.

FIVE KEYS, EVERY LEFT ROW KEPT, AND ONLY ONE BAR LANDS ON THE LINE 0 10 20 30 40 50 rows out 50 18 added 46 14 added 34 34 32 the 32 rows that went in the stall alone the stall name alone the stall and the month the name and the month the stall and month, day book collapsed Green is the part of each bar that stands for the record itself. Red is what the join added on top of it. The third and fourth bars are identical in height and are not doing identical work, which the next figure takes apart.
Keeping every left row still gives 50, 46, 34, 34 and 32 rows out of the same 32 rows going in, so only the last key returns the record it was handed and the other four all hand back something larger.
TWO KEYS, ONE ROW COUNT, AND NOT THE SAME WORK WHAT IS CHECKED THE STALL AND THE MONTH THE NAME AND THE MONTH Rows out of 32 in 34 34 Rows carrying a note 13 12 Which stall month comes out with an empty note none of them NB-03 month 3 and the day book wrote one for it A row count check passes both of these keys with the same answer, so the row count caught nothing here. The count of rows that actually found something on the right is the second check, and it costs one more line.
Two keys both return 34 rows while one attaches a note to 13 of them and the other to only 12, so a row count alone cannot tell them apart and NB-03 month 3 comes out empty without any warning.
Try it out

Two of the five keys both return 34 rows out of the same 32 going in. Are they doing the same thing?

Building a Comparable Companies Table teaches you to build a peer set you can defend and a multiple that means something.

What is the fix, and how long does it take?

One line, and a decision somebody has to make. The trouble was never the join; it was that the right hand table had a grain the join was not told about. So the grain has to be supplied. Exactly one stall month held two notes. Collapsing the day book to one row per stall month before joining anything, keeping the later note by its day, takes the day book from 12 rows to 11.

Then join on the stall and the month, keeping every left row. Thirty two rows go in and 32 rows come out. The takings total is Rs 21,40,099/- going in and Rs 21,40,099/- coming out. The average takings per stall month is Rs 69,035.45/- going in and Rs 69,035.45/- coming out. The record comes back unchanged, with a note attached to the 11 stall months that have one and an empty note on the 21 that do not.

The fix is not a technique but a decision. Somebody has to say which of NB-03 month 2's two notes was meant, and no join anywhere can say it for them. The day 19 note is the one recording a revision, and a first figure withdrawn, so keeping the later note by its day is a defensible answer. Keeping the earlier one would be a different answer and would need a different reason. Keeping both is what was happening by accident, and it is the only one of the three that nobody chose.

# the two tables, exactly as printed above, held as plain rows
returns = [("NB-03", 2, "Harit Greens", 36400, 5),
           ("NB-03", 2, "Harit Greens", 39700, 19)]   # and 30 more, 32 rows
daybook = [("NB-03", 2, 5, "return filed"),
           ("NB-03", 2, 19, "return revised")]      # and 10 more, 12 notes

# the count before, which costs nothing and is the whole method
print(len(returns), len(daybook))          # 32 12

# the join everybody writes first, on the column both tables obviously share
wide = left_join(returns, daybook, on=["stall_id"])
print(len(wide))                            # 50
print(total_takings(wide))                  # 3306299, against 2140099

The count before and the count after, around the join on the stall alone. Every row it runs on is printed in the two tables further up, and the two printed counts are the only warning this operation will ever give.

# give the right hand table the grain the join needs: one row per stall month
collapsed = keep_latest_by_day(daybook, on=["stall_id", "month"])
print(len(collapsed))                       # 11, down from 12

# now the same join, on the stall and the month, keeping every left row
fixed = left_join(returns, collapsed, on=["stall_id", "month"])
print(len(returns), len(fixed))            # 32 32
print(total_takings(fixed))                 # 2140099, exactly what went in
print(matched_rows(fixed))                  # 11 rows carry a note, 21 carry none

The fix, and the second check beside it. Collapsing the day book to one row per stall month is the line that decides which note was meant, and printing the count of rows that carry a note is what separates this key from the one that also returns 34.

TWO STEPS, IN ORDER, AND THE FIRST ONE IS A DECISION STEP ONE, COLLAPSE THE DAY BOOK NB-03 month 2 day 5 return filed NB-03 month 2 day 19 return revised Keep the later note by its day, because it records the revision that withdrew the first. 12 notes become 11 rows STEP TWO, JOIN ON THE STALL AND MONTH 32 rows in, 32 rows out Rs 21,40,099/- in, Rs 21,40,099/- out Rs 69,035.45/- in, Rs 69,035.45/- out 11 rows carry a note, 21 carry none, and none of them was dropped. Step two without step one gives 34 rows out of 32, because the one stall month holding two notes writes its two returns out four times. The order of the two steps is the whole fix. Step one is not a technique. It is somebody deciding which of two notes was meant, and writing the reason down. A join has no opinion about that and will happily keep both when nobody chooses.
Collapsing the day book to one row per stall month before joining returns 32 rows out of 32 in with the total and the average unchanged, and choosing which note to keep is a decision rather than a technique.
Try it out

The fix collapses the day book from 12 rows to 11 by keeping the later note. What decision was actually made there, and who is best placed to make it?

Reading an Option Payoff — free micro-course from Fin Maverick

What gets written down before anything is computed from a joined table?

A lender pulling a borrower's monthly sales alongside a log of missed instalments, an analyst putting a price history beside a table of corporate events, a shopkeeper putting last year's stock sheet next to this year's, and a household laying its bank statement beside its own list of what each payment was for: all four are doing the same operation on two tables with different grains, and all four can silently double a figure they care about. Four lines protect every one of them, and the four lines cost nothing.

  1. The rows going in. Thirty two. Written before the join runs, not after, because afterwards it has to be reconstructed from a table that has already changed.
  2. What one row of the right hand table describes, as a sentence. One row describes one note. When that sentence is not the same shape as the left hand one, the count will move, and the reason it moves is already apparent.
  3. The rows coming out, and of those, how many found something on the right. Two numbers, not one. Thirty four and 13 is a different result from 34 and 12, and only the second number can tell them apart.
  4. One sentence saying why the counts differ, written before any arithmetic. On this record: 21 rows found no note and were kept with an empty one, and 2 rows were added where one stall month carried two notes.

A total computed after an unchecked join is not slightly uncertain. It is a figure with no known denominator, and nobody afterwards can say how many things it was a total of. The named convention that made the average defensible in the first place quietly stopped being true the moment the row count changed, and nothing on the screen marked the moment it stopped.

Try it out

A colleague sends over a table built by joining two others and says it looks fine. Which three things get written down before anything is computed from it?

The failure: a correct join, a corrupted report, and nothing to see for months

An analyst is asked for the Neelbagh market's takings alongside the office's notes. Both tables are to hand. The stall code is the column they obviously share, so the join goes on the stall code, runs without complaint, and returns a wider table with a note beside almost every row. Nothing about the table is malformed, so nothing about it looks wrong.

The takings total on that table is Rs 33,06,299/- where the record holds Rs 21,40,099/-. The overstatement is Rs 11,66,200/-, or 54.4928 per cent. Had the report been a total, somebody would have queried it inside a day: a market of eight stalls does not take half as much again as it took last time anybody looked, and a figure that size draws attention on its own.

The report was an average. Under the named convention the same corrupted table reads Rs 68,881.23/- against a true Rs 69,035.45/-, down Rs 154.22/-, or 0.2234 per cent. The corrupted average is not merely undetectable, it is comfortable. The figure sits exactly where a month of ordinary trading would sit. The move runs in the direction a cautious reader half expects. The figure survives a sanity check, a comparison with last month, and a glance from somebody who knows the market well. So the joined table stayed in use, everything built on it inherited the same corruption, and the cost was never the Rs 154.22/-. The cost was the months during which nobody had any reason to look.

The check that would have caught it on the first day: 32 rows went in and 50 came out. One line, written before any arithmetic, and the whole thing is over.

What sits outside this subject. The three doorways a record arrives through, and the table the work happens in, are settled elsewhere and are not reopened here. Asking a stored table a precise question is covered separately, as is the run of ordered cells that makes an answer reproducible months later. Whether a figure inside the record is wrong, and which of two filings of the same month to believe, is covered separately too: both filings of NB-03 month 2 are taken here exactly as the office wrote them, and neither is judged.

A joined table gave a total with no denominator. See what gets written down.

Where does every count come from?

What the count rests onWhere it sitsSite
The returns table, 32 rows and 31 usable takings figuresPrinted in full above, in the order the market office typed itNone. Nothing outside these two tables was read
The day book, 12 notes over 11 stall monthsPrinted in full above, in the order the office wrote itNone. Nothing outside these two tables was read
Every row count here: 50, 46, 44, 34, 34, 32, 13, 12 and 11Arithmetic on those two tables, redoable by handNone
Rs 33,06,299/-, Rs 11,66,200/-, 54.4928 per cent, Rs 154.22/- and 0.2234 per centSums and one average over the rows each join returns, held exactly and rounded a single time at the finishNone
Any rate, threshold, filing period or published standardNot one is asserted anywhere aboveNone to name

Neelbagh market, its stall record, its office and its day book are invented.
Educational material. Not advice on any investment, tax, budget or market position.

← PreviousNext →
Fin Maverick Micro CoursesExplore Micro Courses
Fin Maverick BootcampsExplore Bootcamps
Fin Maverick

Finance education that ends in a job, not a certificate that gathers dust. Built for young India.

LEARN
CalculatorsFrameworksComparisonsCareersShowdown
RESOURCES
All CoursesMicro CoursesBootcampsInternships
COMPANY
AboutJob openingPartnership
LEGAL
Privacy PolicyTerms & ConditionsContent LicenseReturn & Refund Policy
© 2026 FIN MAVERICK / BUILT FOR INDIA.DO FINANCE, DO NOT JUST READ ABOUT IT.