Who is adding card acceptance? POS terminal growth from RBI files
In this article (8 sections)
Related programme: the Data Science course (6 months; ₹1,30,000 (inclusive of GST) · EMI from ₹5,000/month).
RBI's monthly bank-wise statistics include how many point-of-sale (POS) terminals each bank has deployed. Over a year or two, the series shows which banks and bank groups are building card acceptance. It also shows restatements, mergers and one-off jumps that make naive growth rates misleading. This project builds a bank-month panel, detects structural breaks automatically and estimates growth with methods that resist outliers. It then decomposes national growth into bank-group contributions. It is a small panel-data problem with a clear business reading, which makes it a good data science portfolio piece.
Source
RBI's Bank-wise ATM/POS/Card Statistics page lists one Excel and one PDF file per month, with August 2026 the latest when we checked. Some months are marked "Revised", and at least one file name does not match its listed month: the November 2025 entry uses a file named for October 2025. Take each file's month from its contents.
We could not script the Excel download, because the server returned a browser check. Download the files by hand. We have not confirmed the column layout, so check the downloaded file's header for the POS terminal columns. The file may separate terminal types, so find the total or decide which types you are counting.
Why a payments team would care
Acquiring banks, payment aggregators and terminal vendors compete for merchants. Knowing which issuers are growing acceptance, and whether that growth is organic or from an acquisition, informs sales targeting and partnership decisions. The data science content is the robustness. A single restated month can make a bank look like it tripled its network, and the job is to catch that before a slide goes to leadership.
Three problems in the raw panel
- 1Mergers. When one bank absorbs another, the acquirer's terminal count jumps and the target disappears. Build a merger table (acquirer, target, effective month) and, for growth, combine the two banks' histories before the merger into one pro forma series.
- 2Restatements and revised files. A revised month can differ from the original. Prefer the revised file, keep the original for comparison, and log differences.
- 3Reporting glitches. A single month far off trend that reverts the next month is usually a reporting error, not a real event. Flag it and do not let it drive growth estimates.
Code: break detection and robust growth
The panel below is synthetic. Bank names are placeholders, and the counts, jumps and glitch were planted by us to test the methods.
import numpy as np
import pandas as pd
rng = np.random.default_rng(17)
months = pd.period_range("2024-09", "2026-08", freq="M")
rows = []
for bank, group, start, growth in [("Bank A", "Private", 400_000, 0.020), ("Bank B", "Private", 250_000, 0.012),
("Bank C", "Public", 150_000, 0.004), ("Bank D", "Public", 60_000, 0.001)]:
level = start
for m in months:
level *= 1 + growth + rng.normal(0, 0.004)
rows.append({"bank": bank, "group": group, "month": m, "pos": int(level)})
df = pd.DataFrame(rows)
# Planted events: a one-month reporting glitch and a step up from an acquisition
df.loc[(df["bank"] == "Bank B") & (df["month"] == pd.Period("2025-06", "M")), "pos"] *= 3
df.loc[(df["bank"] == "Bank C") & (df["month"] >= pd.Period("2025-11", "M")), "pos"] += 40_000
df = df.sort_values(["bank", "month"])
df["dlog"] = df.groupby("bank")["pos"].transform(lambda s: np.log(s).diff())
def flag(g):
med = g["dlog"].median()
mad = (g["dlog"] - med).abs().median() * 1.4826
z = (g["dlog"] - med) / mad
nxt = z.shift(-1)
g = g.assign(z=z.round(1))
glitch = (z.abs() > 6) & (nxt.abs() > 6) & (np.sign(z) != np.sign(nxt))
reversal = glitch.shift(1, fill_value=False)
g["event"] = np.select([glitch, reversal, z.abs() > 6], ["glitch", "reversal", "step"], "")
return g
df = df.groupby("bank", group_keys=False)[df.columns].apply(flag)
events = df[df["event"] != ""]
print(events[["bank", "month", "pos", "z", "event"]].to_string(index=False))
# Robust annual growth: median monthly log change, ignoring every flagged month
clean = df[df["event"] == ""]
growth = clean.groupby("bank")["dlog"].median().mul(12).apply(np.expm1).mul(100).round(1)
naive = df.groupby("bank")["pos"].agg(lambda s: (s.iloc[-1] / s.iloc[-13] - 1) * 100).round(1)
print(pd.DataFrame({"robust_annual_growth_pct": growth, "naive_last_12m_pct": naive}))The median absolute deviation (MAD) makes the z-score robust. A single huge jump barely moves the median, so the jump stands out. A glitch shows as a large move followed by an equally large move back, while a step does not reverse. The robust growth estimate uses the median monthly change on clean months. For Bank C, the naive twelve-month figure includes the acquisition, and the robust figure does not. Report both, and say which answers which question.
Decomposing national growth
Total terminals across all banks grow by the sum of each bank's change. So each group's contribution to national growth is its change divided by the starting national total. Contributions add up exactly to national growth, which gives you a built-in check. Show the decomposition twice: once as reported, and once pro forma, with acquisitions removed from the acquirer's growth.
Analysis plan
- Collect 24 months of files and build the bank-month panel of total POS terminals.
- Apply the merger table and produce pro forma histories.
- Run break detection. Hand-check every flagged event against news or RBI notes where possible, and record your verdict.
- Estimate robust growth for each bank with enough history. Bootstrap by month to get an interval if you want to rank banks.
- Decompose national growth by bank group, reported and pro forma.
Deliverables
| Item | Contents |
|---|---|
| Event log | Bank, month, z-score, automatic label, your verdict, evidence |
| Growth table | Bank, group, robust annual growth, interval, naive growth |
| Stacked bar | Contribution to national terminal growth by group, reported vs pro forma |
| Line chart | Three example banks, raw and cleaned, with events marked |
| README | Source, months, revised files used, merger table, thresholds, limitations |
Interview questions
- Why use MAD instead of the standard deviation for flagging?
- How does your method tell a glitch from a genuine step change?
- What is a pro forma series, and when is it the wrong thing to show?
- Your threshold is six robust standard deviations. How did you choose it, and what happens at four?
- How would you extend this to forecast next quarter's terminals by bank?
The card usage by bank project covers the cleaning of these same files in more detail. Robust statistics and anomaly handling are part of our Data Science programme. Take the skill check to see where you would start.
Pankit Kumar has 10 years in Data Science & AI, building and shipping production systems in regulated pharma and clinical environments. He is a freelance trainer at Boston Institute of Analytics, AnalytixLabs and Scaler, and has taught this material to thousands of working professionals.
This article is part of our Data Science programme — 6 months. From data foundations to machine learning, deep learning and deployment.
Explore Data Science