What Indians pay for on UPI: a merchant category code project
In this article (7 sections)
Related programme: the Data Analytics with Generative AI course (3–4 months; ₹90,000 (inclusive of GST) · EMI from ₹4,000/month).
NPCI publishes monthly UPI merchant payments split by merchant category code (MCC), from groceries and fast food to fuel stations and pharmacies. That lets you answer a question most people only guess at: which kinds of merchants account for UPI's merchant volume and value, and how do their typical payment sizes differ? This project builds a monthly MCC table, groups the codes into readable segments, measures concentration, and reconciles the MCC totals against NPCI's person-to-merchant (P2M) figures.
Two tables on one page
Both come from NPCI's UPI Ecosystem Statistics, which has a month selector and a Download control.
Merchant Category Classification has these columns: Type, MCC, Description, Volume (in Mn), Value (in Cr.). "Type" groups the rows into bands such as "High Transacting Categories" and "Medium Transacting…". The Type label appears only on the first row of each band in the on-screen table, and the rows beneath it are blank. The descriptions are standard MCC names, for example 5411 "Groceries and supermarkets", 5812 "Eating places and restaurants" and 5541 "Service stations (with or without ancillary services)".
P2P and P2M Transactions has a two-level header. Month, then Total, P2P and P2M, each split into Volume (In Mn.) and Value (Cr).
Check whether the downloaded files keep the same layout as the screen. Look at the file's header before you parse it.
Why a product or strategy team would care
Merchant acquiring, credit-on-UPI, loyalty and small-business lending products all start from "where is the money actually moving?". A clean MCC mix with ticket sizes is the kind of market-sizing input a strategy analyst prepares in a first month on the job. It also trains a habit analysts need: checking that category rows add up to the headline figure before building anything on them.
Cleaning steps for this source
- 1Fill the Type band downward. Carry the last non-blank Type onto the blank rows below it.
- 2Keep MCC as text. Codes are four digits, and storing them as integers invites accidents. Keep leading zeros if any appear.
- 3Strip digit grouping from volume and value.
- 4Flatten the two-row header of the P2P/P2M table into names like
p2m_volume_mn. - 5Check whether the MCC table is complete. If it lists only the top categories in each band, its total will fall short of P2M volume. That shortfall is a finding to report, not an error to hide.
Synthetic example: mix, tickets and concentration
All numbers below are synthetic. The MCC codes and descriptions are real standard codes, but the volumes and values are invented.
import pandas as pd
import numpy as np
mcc = pd.DataFrame({
"Type": ["High Transacting Categories", None, None, None, "Medium Transacting Categories", None, None],
"MCC": ["5411", "5814", "5812", "5541", "5912", "5462", "4121"],
"Description": ["Groceries and supermarkets", "Fast food restaurants", "Eating places and restaurants",
"Service stations (with or without ancillary services)", "Drug stores and pharmacies",
"Bakeries", "Taxicabs and limousines"],
"Volume (in Mn)": ["3,000.00", "1,200.00", "1,000.00", "600.00", "250.00", "400.00", "150.00"],
"Value (in Cr.)": ["70,000.00", "15,000.00", "18,000.00", "36,000.00", "11,000.00", "6,000.00", "4,500.00"],
})
mcc["Type"] = mcc["Type"].ffill()
for col, new in [("Volume (in Mn)", "volume_mn"), ("Value (in Cr.)", "value_cr")]:
mcc[new] = pd.to_numeric(mcc[col].str.replace(",", "", regex=False))
segment = {"5411": "Groceries", "5814": "Food service", "5812": "Food service", "5541": "Fuel",
"5912": "Health", "5462": "Food retail", "4121": "Transport"}
mcc["segment"] = mcc["MCC"].map(segment)
assert mcc["segment"].notna().all(), "an MCC has no segment mapping"
seg = mcc.groupby("segment")[["volume_mn", "value_cr"]].sum()
seg["vol_share"] = (seg["volume_mn"] / seg["volume_mn"].sum()).round(3)
seg["avg_ticket_rs"] = (10 * seg["value_cr"] / seg["volume_mn"]).round(0)
print(seg.sort_values("volume_mn", ascending=False))
# Herfindahl-Hirschman index on volume shares across MCCs (0 to 10,000)
shares = mcc["volume_mn"] / mcc["volume_mn"].sum() * 100
print("HHI across listed MCCs:", round(float((shares ** 2).sum())))
# Reconciliation against a synthetic P2M headline
p2m_volume_mn = 7200.0
coverage = mcc["volume_mn"].sum() / p2m_volume_mn
print(f"listed MCCs cover {coverage:.1%} of P2M volume")The assertion on segment mapping is deliberate. If NPCI adds a new code next month, you want the run to stop rather than silently drop that code's volume.
The analysis plan
- Mix by segment: volume and value shares by your segment mapping, for the latest month and for the same month a year earlier.
- Ticket sizes: average rupees per payment by segment. A pairing like fuel and groceries is a useful contrast, because the two differ in how often and how much people pay.
- Concentration: HHI across the listed MCCs by month. Is merchant volume spreading across more categories or piling into a few?
- Reconciliation: the listed MCC total as a share of P2M volume and value, from the P2P/P2M table. Publish this number prominently.
- P2M share over time: P2M ÷ total volume by month, as the backdrop to the category story.
What you hand in
| Artefact | Specification |
|---|---|
| Treemap or sorted bar | Segments by volume share, labelled with average ticket |
| Line chart | P2M share of total UPI volume by month |
| Table | Top 15 MCCs: code, description, volume, value, average ticket |
| Mapping file | mcc_segments.csv, reviewed by hand, with a column for your reasoning |
| README | Source, tab names, months, reconciliation coverage, segment rules, limits |
The limits section should say that an MCC is how a merchant is classified, which may not match what a customer actually bought. A "grocery" merchant can sell almost anything.
Questions you should rehearse
- 1Why store MCC as a string?
- 2The MCC rows cover less than 100% of P2M. What could explain the gap?
- 3What does HHI tell a business reader, and how would you explain it without the formula?
- 4How would you decide whether a mixed-category merchant belongs in "food" or "retail"?
- 5If a credit-on-UPI product team asked for a target segment, what would you recommend from this, and what would you want to check first?
This pairs naturally with the ticket size trend project, which explains the headline side of the same story. Segment mapping, reconciliation and clear business charts are all part of our Data Analyst programme. Ask a counsellor on the contact page for current batches and fees.
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 Analytics with Generative AI programme — 3–4 months. The full analyst stack — Excel, SQL, Power BI and Python pipelines — then a generative-AI layer you can prove is right.
Explore Data Analytics with Generative AI