Financial Ratio Analysis for Valuation
Compute, interpret, and automate four families of financial ratios — liquidity, solvency, profitability, and efficiency — to diagnose a company's financial health before you value it.
Learning Objectives
- Compute all four families of financial ratios — liquidity, solvency, profitability, and efficiency — from raw financial statements
- Interpret each ratio in the context of valuation: what it reveals about a company's competitive position and risk profile
- Build Python functions that automate ratio computation for any company using yfinance data
- Compare ratios across peer companies and identify outliers that signal competitive advantage or distress
- Understand the Indian context — sector-specific benchmarks and common ratio patterns in Indian industries
- Excel track: Compute all four ratio families with spreadsheet formulas in the Session 3 workbook — no coding required
This session computes four ratio families. The Python path (marked Python) automates them for any company; the parallel Excel path (green Excel Path boxes) computes the identical ratios with formulas — and one genuine advantage: Screener.in already reports many of these ratios (OPM, ROCE, Debt/Equity), so Excel users can cross-check their formulas against the website's pre-computed values. If your formula result matches Screener.in, your formula is right.
🔍 Opening Challenge — Doctor's Diagnosis
Patient A: Current Ratio 0.6, Interest Coverage 4.5x
Patient B: Current Ratio 3.5, Debt-to-Equity 0.05, Gross Margin 45%
Patient C: Current Ratio 1.1, Interest Coverage 0.7x
Patient D: Current Ratio 2.0, Inventory Turnover 12x, Negative Cash Conversion Cycle
4.1 Why Ratio Analysis Precedes Valuation
A ratio is simply one financial number divided by another. But that simplicity is deceptive. Ratios are the diagnostic toolkit of the valuation practitioner — they reveal a company's financial architecture, its competitive strengths, its vulnerabilities, and the sustainability of its earnings. Before you forecast a single number, you must understand the company through its ratios.
Ratio analysis serves three purposes in the valuation workflow:
We organize ratios into four families, each answering a different question about the business:
| Family | Question Answered | Key Users |
|---|---|---|
| Liquidity Ratios | Can the company pay its bills over the next 12 months? | Creditors, suppliers, short-term lenders |
| Solvency / Leverage Ratios | Can the company survive over the long term? Is its debt load sustainable? | Bondholders, banks, rating agencies |
| Profitability Ratios | How efficiently does the company convert revenue into profit? | Equity investors, analysts, management |
| Efficiency / Activity Ratios | How well does the company manage its assets and working capital? | Operations managers, private equity, lenders |
For each question, identify which ratio family answers it:
- "Can this company pay its suppliers in the next 6 months?" → ___
- "Is this company's debt load sustainable over a decade?" → ___
- "How much profit does this company make per rupee of sales?" → ___
- "How quickly does this company turn inventory into cash?" → ___
Reveal answers
1) Liquidity · 2) Solvency · 3) Profitability · 4) Efficiency.
4.2 Liquidity Ratios: Can the Company Survive Tomorrow?
Liquidity ratios measure a company's ability to meet its short-term obligations as they fall due. A company can be highly profitable on paper and still go bankrupt if it runs out of cash. Liquidity is about survival.
4.2.1 Current Ratio
The current ratio is the broadest liquidity measure. A ratio of 2.0 means the company has Rs. 2 of current assets for every Rs. 1 of current liabilities. The traditional rule of thumb is 2.0, but this varies enormously by industry:
- Manufacturing (Asian Paints, Tata Motors): 1.5–2.5 is typical — inventory and receivables are substantial.
- IT Services (TCS, Infosys): 3.0–5.0 is common — high cash balances, minimal inventory.
- Retail (Titan, Avenue Supermarts): 0.8–1.5 is normal — retailers collect cash from customers before paying suppliers, so they operate with negative working capital.
4.2.2 Quick Ratio (Acid Test)
The quick ratio strips out inventory — the least liquid current asset. It answers: if all sales stopped tomorrow, could the company pay its immediate bills? A quick ratio below 1.0 is a warning sign except in industries where inventory turns rapidly (FMCG, retail). For a steel company, however, a quick ratio of 0.4 signals potential distress — steel inventory cannot be liquidated quickly at book value.
4.2.3 Cash Ratio
The most conservative liquidity measure. It asks: can the company pay all its short-term obligations with cash on hand alone? Most healthy companies have a cash ratio well below 1.0 — they do not keep idle cash equal to all their current liabilities. A very high cash ratio (above 1.5) may indicate poor capital allocation — money sitting in low-yield bank accounts rather than being invested in the business or returned to shareholders.
4.2.4 Python Implementation
def compute_liquidity_ratios(balance_sheet):
"""
Compute current, quick, and cash ratios from balance sheet data.
Parameters
----------
balance_sheet : pd.DataFrame
yfinance balance_sheet (columns = fiscal years)
"""
def safe_get(df, candidates):
for c in ([candidates] if isinstance(candidates, str) else candidates):
if c in df.index:
return df.loc[c]
return None
current_assets = safe_get(balance_sheet, [
'Current Assets', 'Total Current Assets'])
current_liabilities = safe_get(balance_sheet, [
'Current Liabilities', 'Total Current Liabilities'])
inventory = safe_get(balance_sheet, ['Inventory', 'Inventories'])
cash = safe_get(balance_sheet, [
'Cash And Cash Equivalents',
'Cash Cash Equivalents And Short Term Investments'])
ratios = {}
if current_assets is not None and current_liabilities is not None:
ratios['Current Ratio'] = (current_assets / current_liabilities).round(2)
if current_assets is not None and inventory is not None and current_liabilities is not None:
ratios['Quick Ratio'] = ((current_assets - inventory) / current_liabilities).round(2)
if cash is not None and current_liabilities is not None:
ratios['Cash Ratio'] = (cash / current_liabilities).round(2)
return pd.DataFrame(ratios)
# Usage
import yfinance as yf
ticker = yf.Ticker("ASIANPAINT.NS")
liquidity = compute_liquidity_ratios(ticker.balance_sheet)
print(liquidity)
Given this balance sheet snapshot (Rs. Cr):
- Cash: 50 · Receivables: 120 · Inventory: 180
- Current Liabilities: 250
Compute all three liquidity ratios:
- Current Ratio = ?
- Quick Ratio = ?
- Cash Ratio = ?
Interpret: Is this company comfortably liquid? Which ratio reveals the concern?
Reveal answers
Current = (50+120+180)/250 = 1.4 · Quick = (50+120)/250 = 0.68 · Cash = 50/250 = 0.20. The current ratio looks OK, but the quick ratio of 0.68 reveals heavy dependence on inventory being sold — a concern if sales slow.
On each company's Drivers sheet (Session 3 workbook), add three rows below your balance sheet items. Assume: Current Assets in row 20, Inventory in row 22, Cash in row 6, Current Liabilities in row 21 (adjust to your layout):
= C20 / C21
' Quick Ratio — subtract inventory (row 31):
= (C20 - C22) / C21
' Cash Ratio (row 32):
= C6 / C21
' Wrap all in IFERROR to survive missing data:
=IFERROR(C20/C21, "n/a")
Enter in the newest-year column, drag right for history — you now have the 5-year liquidity trend the Python function produces. Then repeat on every company sheet.
Screener.in cross-check: the site's Balance Sheet table shows Current Assets and Current Liabilities directly — verify one year of your Current Ratio by hand against these two numbers.
4.3 Solvency / Leverage Ratios: Can the Company Survive the Long Term?
Solvency ratios measure a company's ability to meet its long-term debt obligations. While liquidity is about the next 12 months, solvency is about the next decade. High leverage magnifies returns in good times — and magnifies losses in bad times.
4.3.1 Debt-to-Equity Ratio
This is the most widely used leverage ratio. A D/E of 1.0 means the company has equal amounts of debt and equity financing. Indian companies tend to have higher D/E ratios than their US counterparts due to historically higher interest rates making equity financing expensive, and promoter preference for retaining control through debt rather than equity dilution.
Indian sector benchmarks:
| Sector | Typical D/E Range | Notes |
|---|---|---|
| IT Services | 0.0–0.2 | Asset-light, cash-rich — TCS and Infosys are virtually debt-free |
| FMCG / Consumer | 0.0–0.5 | Strong cash flows; little need for debt financing |
| Pharma | 0.2–0.8 | R&D and acquisitions drive debt; Sun Pharma ~0.3, Dr. Reddy's ~0.15 |
| Automotive | 0.5–1.5 | Capital-intensive; Tata Motors carries significant JLR debt |
| Telecom | 1.5–3.0+ | Spectrum costs and infrastructure drive high leverage; Bharti Airtel ~2.0 |
| Infrastructure / Power | 2.0–5.0+ | Project finance model; high leverage is structural, not necessarily distressed |
4.3.2 Interest Coverage Ratio (ICR)
ICR measures how many times the company can pay its interest obligations from its operating profit. It is arguably more important than D/E because it directly answers the question: can the company service its debt?
- ICR > 5: Comfortable — the company generates ample earnings to cover interest.
- ICR 2–5: Manageable but warrants monitoring — a downturn could strain coverage.
- ICR 1–2: Stressed — a modest earnings decline could make interest payments difficult.
- ICR < 1: Crisis — the company is not generating enough operating profit to cover interest. It is borrowing to pay interest or dipping into reserves.
4.3.3 Debt-to-EBITDA
This ratio measures how many years of EBITDA it would take to pay off all the debt, assuming EBITDA could be entirely devoted to debt repayment. A ratio above 4–5x is generally considered aggressive. Private equity firms routinely model this ratio as a key covenant metric.
4.3.4 Python Implementation
def compute_solvency_ratios(balance_sheet, income_stmt):
"""Compute debt-to-equity, interest coverage, and debt-to-EBITDA."""
def safe_get(df, candidates):
for c in ([candidates] if isinstance(candidates, str) else candidates):
if c in df.index:
return df.loc[c]
return None
total_debt = safe_get(balance_sheet, ['Total Debt', 'Long Term Debt'])
equity = safe_get(balance_sheet, [
'Total Equity Gross Minority Interest',
'Stockholders Equity', 'Total Equity'])
ebit = safe_get(income_stmt, ['Operating Income', 'EBIT'])
interest_expense = safe_get(income_stmt, ['Interest Expense',
'Interest Expense Non Operating'])
ebitda = safe_get(income_stmt, ['EBITDA'])
# If EBITDA not directly available, estimate
if ebitda is None and ebit is not None:
depreciation = safe_get(income_stmt, [
'Depreciation And Amortization',
'Depreciation Amortization Depletion'])
if depreciation is not None:
ebitda = ebit + depreciation
ratios = {}
if total_debt is not None and equity is not None:
ratios['Debt-to-Equity'] = (total_debt / equity).round(2)
if ebit is not None and interest_expense is not None:
# Avoid division by zero
icr = ebit / interest_expense.replace(0, np.nan)
ratios['Interest Coverage'] = icr.round(2)
if total_debt is not None and ebitda is not None:
ratios['Debt-to-EBITDA'] = (total_debt / ebitda).round(2)
return pd.DataFrame(ratios)
Compute the Interest Coverage Ratio for each company and classify its financial health:
| Company | EBIT (Rs. Cr) | Interest Expense (Rs. Cr) |
|---|---|---|
| Alpha Industries | 400 | 80 |
| Beta Infra | 200 | 240 |
| Gamma Retail | 90 | 60 |
For each: (a) ICR = ? (b) Comfortable / Manageable / Stressed / Crisis?
Reveal answers
Alpha: ICR 5.0 → Comfortable. Beta: ICR 0.83 → Crisis (can't cover interest). Gamma: ICR 1.5 → Stressed. Beta is the Vodafone-Idea pattern — borrowing to pay interest.
Same workbook, three more rows. Assume: Operating Profit row 3, Interest cost row 24 (Screener.in P&L line), Total Debt row 5, Equity row 7, EBITDA row 25:
= C5 / C7
' Interest Coverage (row 34) — Excel's IF replaces our Python if/else:
=IF(C24=0, "No debt", C3 / C24)
' Debt-to-EBITDA (row 35):
=IFERROR(C5 / C25, "n/a")
Bonus — the traffic light (conditional formatting): select the ICR row → Home → Conditional Formatting → Highlight Cell Rules → Less Than 1 → red fill. Add a second rule: Greater Than 5 → green fill. Now distress jumps off the sheet in color — the Excel equivalent of the classification table above, applied automatically to every company and year.
Screener.in cross-check: the "Debt / Equity" ratio is printed on every company page — your row 33 should match it.
4.4 Profitability Ratios: How Much Money Does the Business Actually Make?
Profitability ratios measure a company's ability to generate profit relative to its revenue, assets, and equity. They are the most directly valuation-relevant family of ratios because profit generation is the engine that drives free cash flow.
4.4.1 Margin Ratios
We introduced margins in Chapters 2 and 3. Here we formalize them into a unified framework and add the interpretation layer.
| Ratio | Formula | What It Reveals |
|---|---|---|
| Gross Margin | (Revenue − COGS) / Revenue | Pricing power and production efficiency. Declining gross margins signal competitive pressure or rising input costs that cannot be passed on to customers. |
| Operating Margin | EBIT / Revenue | The profitability of the core business before financing and tax decisions. The single most important margin for DCF — it feeds directly into free cash flow projections. |
| Net Margin | Net Income / Revenue | What ultimately accrues to shareholders. But it is contaminated by financing choices, tax planning, and non-operating items. Use with caution. |
| EBITDA Margin | EBITDA / Revenue | A proxy for operating cash flow margin. Wide EBITDA margins (>30%) indicate a capital-light, high-pricing-power business. |
4.4.2 Return Ratios
While margins measure profit per rupee of revenue, return ratios measure profit per rupee of investment. For valuation, return ratios are more important than margins because they capture capital efficiency.
Return on Assets (ROA):
ROA measures how efficiently the company uses its total asset base to generate profit. ROA varies dramatically by business model: an IT services company with minimal physical assets might show 15–20% ROA, while a steel manufacturer with massive PPE might show 3–5%. Comparing ROA across sectors is meaningless; comparing ROA within a sector identifies the most asset-efficient operators.
Return on Equity (ROE):
ROE measures the return earned on shareholders' capital. It is the most watched profitability metric by equity investors. However, ROE can be inflated by leverage — a company can boost ROE simply by taking on more debt. This is the DuPont effect.
4.4.3 The DuPont Decomposition
The DuPont formula decomposes ROE into three drivers, revealing why ROE is high or low:
ROE = Net Margin × Asset Turnover × Equity Multiplier
This decomposition is invaluable for valuation. A company with a 25% ROE achieved through a 20% net margin and low leverage (Titan) is fundamentally higher quality than a company with a 25% ROE achieved through a 5% margin and 5x leverage (an infrastructure developer). The former's earnings are sustainable; the latter's are fragile.
4.4.4 Python Implementation
def compute_profitability_ratios(income_stmt, balance_sheet):
"""Compute margins, ROA, ROE, and DuPont decomposition."""
def safe_get(df, candidates):
for c in ([candidates] if isinstance(candidates, str) else candidates):
if c in df.index:
return df.loc[c]
return None
rev = safe_get(income_stmt, ['Total Revenue', 'Revenue'])
gp = safe_get(income_stmt, ['Gross Profit'])
ebit = safe_get(income_stmt, ['Operating Income', 'EBIT'])
ni = safe_get(income_stmt, ['Net Income', 'Net Income Common Stockholders'])
ebitda = safe_get(income_stmt, ['EBITDA'])
assets = safe_get(balance_sheet, ['Total Assets'])
equity = safe_get(balance_sheet, [
'Total Equity Gross Minority Interest',
'Stockholders Equity', 'Total Equity'])
ratios = {}
if gp is not None and rev is not None:
ratios['Gross Margin (%)'] = (gp / rev * 100).round(2)
if ebit is not None and rev is not None:
ratios['Operating Margin (%)'] = (ebit / rev * 100).round(2)
if ni is not None and rev is not None:
ratios['Net Margin (%)'] = (ni / rev * 100).round(2)
if ebitda is not None and rev is not None:
ratios['EBITDA Margin (%)'] = (ebitda / rev * 100).round(2)
if ni is not None and assets is not None:
ratios['ROA (%)'] = (ni / assets * 100).round(2)
if ni is not None and equity is not None:
ratios['ROE (%)'] = (ni / equity * 100).round(2)
# DuPont decomposition
if ni is not None and rev is not None and assets is not None and equity is not None:
net_margin = ni / rev
asset_turnover = rev / assets
equity_multiplier = assets / equity
ratios['DuPont — Net Margin'] = (net_margin * 100).round(2)
ratios['DuPont — Asset Turnover'] = asset_turnover.round(2)
ratios['DuPont — Equity Multiplier'] = equity_multiplier.round(2)
return pd.DataFrame(ratios)
Two companies both have ROE of 20%. Use the DuPont formula to see WHY:
| Company X | Company Y | |
|---|---|---|
| Net Margin | 15% | 5% |
| Asset Turnover | 1.0 | 2.0 |
| Equity Multiplier | 1.33 | 2.0 |
Compute each company's ROE. Then discuss with your partner: Which company's 20% ROE is more sustainable — and why?
Reveal
X: 15% × 1.0 × 1.33 ≈ 20%. Y: 5% × 2.0 × 2.0 = 20%. Company X's ROE comes from pricing power (high margin); Y's is inflated by leverage (multiplier 2.0). X's is more sustainable — Y's is fragile if rates rise.
Think: A company boosts its ROE from 15% to 22% purely by taking on more debt (higher equity multiplier). Has it actually become a better business?
Pair: Discuss — is the higher ROE "real"? What risk is being added? What happens to ROE if interest rates rise?
Share: We'll hear from 2 pairs. Key insight: leverage inflates ROE but adds risk — this is why valuation analysts compare ROE alongside D/E.
Margins and returns (rows 36–40; Net Profit row 4, Sales row 2, Total Assets row 8, Equity row 7):
= C4 / C2 * 100
' Return on Assets % (row 37):
= C4 / C8 * 100
' Return on Equity % (row 38):
= C4 / C7 * 100
DuPont — build it as three visible rows that multiply into ROE (this is where Excel beats Python for learning, because you SEE the decomposition):
= C4 / C2
' DuPont 2: Asset Turnover (row 40):
= C2 / C8
' DuPont 3: Equity Multiplier (row 41):
= C8 / C7
' ROE check (row 42) — should equal row 38 ÷ 100:
= C39 * C40 * C41
If row 42 ≠ row 38/100 to the 4th decimal, one of your references is wrong — this built-in check is your unit test.
Screener.in cross-check: OPM appears at the top of the P&L table and ROE in the ratios panel — two free validation points for every company.
4.5 Efficiency / Activity Ratios: How Well Is the Company Managed?
Efficiency ratios measure how effectively a company uses its assets and manages its working capital. Two companies with identical margins can have vastly different free cash flows if one ties up twice as much capital in receivables and inventory.
4.5.1 Asset Turnover
This is the broadest efficiency measure. It answers: how many rupees of revenue does the company generate for each rupee of assets? An asset turnover of 2.0 means Rs. 2 of revenue per Rs. 1 of assets. Asset-light businesses (IT services, consulting) have high turnover (1.5–3.0); asset-heavy businesses (steel, power) have low turnover (0.3–0.8).
4.5.2 Working Capital Ratios
These three ratios form the cash conversion cycle — the time between paying suppliers and collecting from customers:
| Ratio | Formula | Interpretation |
|---|---|---|
| Receivable Turnover | Revenue / Accounts Receivable | How many times per year the company collects its receivables. Higher = faster collection. |
| Days Sales Outstanding (DSO) | 365 / Receivable Turnover | Average days to collect from customers. DSO > 90 days is concerning for most industries. Indian PSUs often have DSOs of 120–180 days due to government payment cycles. |
| Inventory Turnover | COGS / Inventory | How many times per year inventory is sold. Higher = faster-moving inventory. |
| Days Inventory Outstanding (DIO) | 365 / Inventory Turnover | Average days inventory sits before being sold. Rising DIO suggests slowing sales or obsolete stock. |
| Payable Turnover | COGS / Accounts Payable | How many times per year the company pays suppliers. |
| Days Payable Outstanding (DPO) | 365 / Payable Turnover | Average days to pay suppliers. A high DPO is good for cash flow (you are using supplier credit) but may signal strained supplier relationships. |
4.5.3 The Cash Conversion Cycle
The CCC measures how many days of working capital financing are needed to run the business. A negative CCC means the company collects from customers before it pays suppliers — it is effectively financed by its suppliers. This is the holy grail of working capital management. DMart (Avenue Supermarts) consistently runs a negative CCC because it collects cash from customers instantly but pays suppliers on 30–45 day terms.
4.5.4 Python Implementation
def compute_efficiency_ratios(income_stmt, balance_sheet):
"""Compute asset turnover and working capital efficiency ratios."""
def safe_get(df, candidates):
for c in ([candidates] if isinstance(candidates, str) else candidates):
if c in df.index:
return df.loc[c]
return None
rev = safe_get(income_stmt, ['Total Revenue', 'Revenue'])
cogs = safe_get(income_stmt, ['Cost Of Goods Sold', 'Cost of Revenue'])
assets = safe_get(balance_sheet, ['Total Assets'])
ar = safe_get(balance_sheet, ['Accounts Receivable',
'Net Receivables', 'Receivables'])
inv = safe_get(balance_sheet, ['Inventory', 'Inventories'])
ap = safe_get(balance_sheet, ['Accounts Payable', 'Payables'])
ratios = {}
if rev is not None and assets is not None:
ratios['Asset Turnover'] = (rev / assets).round(2)
if rev is not None and ar is not None:
ratios['Receivable Turnover'] = (rev / ar).round(2)
ratios['Days Sales Outstanding'] = (365 / ratios['Receivable Turnover']).round(0)
if cogs is not None and inv is not None:
ratios['Inventory Turnover'] = (cogs / inv).round(2)
ratios['Days Inventory Outstanding'] = (365 / ratios['Inventory Turnover']).round(0)
if cogs is not None and ap is not None:
ratios['Payable Turnover'] = (cogs / ap).round(2)
ratios['Days Payable Outstanding'] = (365 / ratios['Payable Turnover']).round(0)
# Cash Conversion Cycle
if all(k in ratios for k in ['Days Sales Outstanding',
'Days Inventory Outstanding',
'Days Payable Outstanding']):
ratios['Cash Conversion Cycle'] = (
ratios['Days Sales Outstanding'] +
ratios['Days Inventory Outstanding'] -
ratios['Days Payable Outstanding'])
return pd.DataFrame(ratios)
For two retailers:
| Days | DMart-style | Typical Retailer |
|---|---|---|
| Days Sales Outstanding | 2 | 15 |
| Days Inventory Outstanding | 28 | 60 |
| Days Payable Outstanding | 45 | 30 |
Compute CCC = DSO + DIO − DPO for both. Which company is financed by its suppliers? What does a negative CCC mean?
Reveal answers
DMart: 2 + 28 − 45 = −15 days (negative — collects from customers before paying suppliers). Typical: 15 + 60 − 30 = 45 days. DMart needs no working capital — a huge competitive advantage.
Working capital items needed: Receivables row 26, Inventory row 22, Payables row 27, plus Sales (row 2) and COGS — if your Data sheet lacks a COGS row, add it from Screener.in's P&L ("Raw material cost" + "Other expenses" is a common approximation; use "Total Expenses" − "Employee cost" if needed):
= 365 * C26 / C2
' Days Inventory Outstanding (row 44):
= 365 * C22 / COGS_cell
' Days Payable Outstanding (row 45):
= 365 * C27 / COGS_cell
' Cash Conversion Cycle (row 46):
= C43 + C44 - C45
Why 365 ×: the formula 365 × (Receivables / Sales) is just "days = 365 ÷ turnover" rearranged — identical to the Python version, easier to audit in a cell.
Instant benchmark: apply conditional formatting to the CCC row: Less Than 0 → green (supplier-financed business, the DMart pattern). Now scan all your companies' CCC rows at once — the negative ones glow.
4.6 The Complete Ratio Analysis Pipeline
Now we combine all four ratio families into a single, comprehensive analysis function that produces a complete financial diagnostic for any company:
def full_ratio_analysis(ticker_symbol):
"""
Perform a complete financial ratio analysis for any company.
Returns a dict with four DataFrames (liquidity, solvency,
profitability, efficiency) plus the latest-year summary.
"""
ticker = yf.Ticker(ticker_symbol)
info = ticker.info
is_df = ticker.financials
bs_df = ticker.balance_sheet
cf_df = ticker.cashflow
results = {
'ticker': ticker_symbol,
'company': info.get('longName', ticker_symbol),
'sector': info.get('sector', 'N/A'),
'market_cap': info.get('marketCap', None),
'liquidity': compute_liquidity_ratios(bs_df),
'solvency': compute_solvency_ratios(bs_df, is_df),
'profitability': compute_profitability_ratios(is_df, bs_df),
'efficiency': compute_efficiency_ratios(is_df, bs_df),
}
# Build a single-row summary (latest year)
summary = {}
for family in ['liquidity', 'solvency', 'profitability', 'efficiency']:
df = results[family]
if not df.empty:
latest_col = df.columns[0]
for idx in df.index:
summary[idx] = df.loc[idx, latest_col]
results['summary'] = pd.Series(summary).round(2)
return results
def compare_ratios(tickers, labels=None):
"""Compare ratio summaries across multiple companies."""
if labels is None:
labels = [t.replace('.NS', '').replace('.BO', '') for t in tickers]
summaries = {}
for ticker, label in zip(tickers, labels):
try:
r = full_ratio_analysis(ticker)
summaries[label] = r['summary']
print(f"✓ {label}")
except Exception as e:
print(f"✗ {label}: {e}")
time.sleep(1.5)
return pd.DataFrame(summaries)
# --- Example: Compare Indian consumer companies ---
consumer_tickers = ['ASIANPAINT.NS', 'TITAN.NS', 'NESTLEIND.NS', 'BRITANNIA.NS']
consumer_labels = ['Asian Paints', 'Titan', 'Nestle India', 'Britannia']
comparison = compare_ratios(consumer_tickers, consumer_labels)
# Display only key metrics
key_metrics = [
'Current Ratio', 'Debt-to-Equity', 'Interest Coverage',
'Gross Margin (%)', 'Operating Margin (%)', 'Net Margin (%)',
'ROE (%)', 'Asset Turnover', 'Cash Conversion Cycle'
]
print("\n=== Indian Consumer Sector — Key Ratio Comparison ===")
print(comparison.loc[comparison.index.intersection(key_metrics)])
Run full_ratio_analysis("ASIANPAINT.NS") (or your own company). Examine the four ratio DataFrames and answer:
- Is the company liquid? (current ratio)
- Is it solvent? (D/E, interest coverage)
- Is it profitable? (margins, ROE)
- Is it efficient? (CCC — is it negative?)
Now run a second company from a DIFFERENT sector (e.g., a bank or steel company). Compare: which ratios look fundamentally different, and why?
This function crashes for some companies. Find the bug:
def compute_interest_coverage(income_stmt):
ebit = income_stmt.loc['Operating Income']
interest = income_stmt.loc['Interest Expense']
return ebit / interest # crashes for debt-free companies!
With your partner: (1) When does this crash? (2) How would you handle it? (3) What should the ICR be for a company with no debt?
Hint
Interest of 0 → division by zero. Use interest.replace(0, np.nan) or set ICR to infinity/NaN for debt-free companies — a company with no interest has no coverage problem.
The Python full_ratio_analysis() + compare_ratios() combination becomes a Ratios block on your Summary sheet (extend the one from Session 3):
- Columns = companies (B1: TCS, C1: INFY, D1: WIPRO…), plus two extra columns: Peer Median and Peer Max/Min.
- Rows = the ratios you built on the company sheets (Current Ratio, D/E, ICR, Net Margin, ROE, DuPont trio, CCC).
- Pull each value cross-sheet, e.g. Summary!B5 = =TCS!G34 (TCS's latest ICR).
- Add the peer statistics that make outliers visible:
=MEDIAN(B5:D5)
' Best / Worst:
=MAX(B5:D5) =MIN(B5:D5)
' Flag outliers vs the median (the Excel outlier detector):
=IF(ABS(B5-$F5) > 0.5*ABS($F5), "OUTLIER", "")
The project's two charts in Excel:
- ROE vs D/E scatter: on Summary, select the ROE row + D/E row (Ctrl-click) → Insert → Scatter → add data labels → label from row 1 company names. This is the exact Python chart, built by selection.
- Margin comparison bars: select the Net Margin / Op Margin rows → Insert → Clustered Column. Done.
4.7 Interpreting Ratios: The Art Behind the Numbers
Computing ratios is mechanical. Interpreting them requires judgment. Here is a framework for extracting insight from the numbers:
4.7.1 The Three-Lens Test
For every ratio, apply three lenses before drawing a conclusion:
4.7.2 Ratio Interaction Patterns
Ratios do not exist in isolation. Certain combinations of ratios tell a coherent story:
| Pattern | Ratios Observed | Story |
|---|---|---|
| Quality Franchise | High & stable gross margin + High ROE + Low D/E + Negative CCC | The company has pricing power, earns excellent returns without leverage, and is financed by suppliers. Example: Titan, Asian Paints, Nestle India. |
| Efficient Operator | Moderate margins + High Asset Turnover + High ROE | The company competes on operational efficiency, not pricing power. Example: DMart, Indigo. |
| Leveraged Growth | High ROE + High D/E + Low Interest Coverage | Returns are juiced by debt. Sustainable only if growth continues and interest rates stay low. Example: Telecom, infrastructure. |
| Melting Ice Cube | Declining gross margins + Rising DSO + Rising DIO | The company is losing pricing power, struggling to collect from customers, and building unsold inventory. Earnings quality is deteriorating. Investigate immediately. |
| Accounting Manipulation Risk | Widening gap between Net Income growth and Operating Cash Flow growth + Rapidly rising DSO + Falling Asset Turnover | Profits may be fictional. Revenue might be recognized before cash is collected. Common in Indian real estate and infrastructure companies historically. |
Match each company description to a ratio interaction pattern from the table above:
- "Stable 45% gross margin, high ROE with zero debt, pays suppliers after collecting from customers." → ___
- "Profits keep rising, but operating cash flow is shrinking and receivables are exploding." → ___
- "Margins fell 300bp this year, inventory is piling up, and customers are paying slower." → ___
Check your answers
1) Quality Franchise · 2) Accounting Manipulation Risk · 3) Melting Ice Cube. Patterns let you spot trouble from ratios alone — before reading the annual report.
Each of the three lenses has a direct Excel implementation on your Drivers sheet:
| Lens | Excel Feature | How |
|---|---|---|
| Time-series | Sparklines | Select a 5-year ratio row → Insert → Sparklines → Line, place in the row label cell. Every ratio gets a mini trend chart — deteriorating trends visibly flatten or fall. |
| Cross-sectional | Peer Median column | Already built in Section 4.6's Excel Path — add Conditional Formatting → Color Scales on the comparison rows: red (worst) to green (best) across companies, instantly. |
| Absolute benchmark | Threshold rules | Conditional Formatting rules: ICR < 1.5 red (Section 4.3), CCC < 0 green (Section 4.5), Current Ratio < 1 amber. These encode the red-flag tables of this chapter as automatic alarms. |
The "Melting Ice Cube" detector — one formula that fires when margins and days-ratios deteriorate together:
=IF(AND(G36<F36, G43>F43, G44>F44),
"⚠ MELTING ICE CUBE — margin down, DSO & DIO up", "OK")
Copy this detector cell onto every company sheet — it replaces the eyeball test with an automatic alarm for the most dangerous pattern in the table above.
4.8 Ratio Benchmarks for Key Indian Sectors
Ratio benchmarks are context-dependent. Below are indicative ranges for major Indian sectors based on FY2024–25 data. These are starting points for analysis, not rigid targets — every company's business model within a sector can produce different ratio profiles.
| Sector | Current Ratio | D/E | Op Margin (%) | ROE (%) | Asset Turnover |
|---|---|---|---|---|---|
| IT Services (TCS, Infosys, HCL, Wipro) | 2.5–4.5 | 0.0–0.2 | 22–28 | 25–45 | 1.0–1.8 |
| Private Banks (HDFC Bank, ICICI, Kotak, Axis) | N/A* | N/A* | N/A* | 12–18 | N/A* |
| Consumer Staples (HUL, Nestle, Britannia) | 1.2–2.0 | 0.0–0.3 | 18–25 | 25–60 | 1.2–2.5 |
| Consumer Discretionary (Titan, Asian Paints) | 2.0–3.5 | 0.1–0.5 | 15–22 | 20–40 | 1.5–2.5 |
| Automotive (Maruti, Tata Motors, M&M) | 0.8–1.5 | 0.3–1.5 | 8–15 | 8–20 | 0.8–1.5 |
| Pharma (Sun Pharma, Dr. Reddy's, Cipla) | 1.5–3.0 | 0.1–0.6 | 15–25 | 12–22 | 0.6–1.2 |
| Cement (UltraTech, Shree Cement, ACC) | 0.8–1.5 | 0.3–0.8 | 15–22 | 10–18 | 0.4–0.8 |
| Telecom (Bharti Airtel, Reliance Jio) | 0.5–1.2 | 1.0–3.0 | 25–40 | 5–15 | 0.3–0.6 |
| Power / Utilities (NTPC, Power Grid) | 0.8–1.3 | 1.5–3.5 | 25–35 | 10–16 | 0.2–0.5 |
| Metals & Mining (Tata Steel, Hindalco, JSW) | 0.8–1.5 | 0.8–2.0 | 10–20 | 5–20 | 0.4–0.8 |
* Banks and financial institutions use a different ratio framework — capital adequacy, NIM, NPA ratios, and CASA ratio — which we address separately in later chapters.
compare_ratios() function from Section 4.6 against a current peer set. The Python pipeline you built is the ultimate source of up-to-date benchmarks.
Without looking at the table, identify the sector for each ratio profile:
- Current Ratio 3.0, D/E 0.1, ROE 35% → ___
- Current Ratio 1.0, D/E 2.0, Op Margin 30% → ___
- Current Ratio 0.9, D/E 0.5, Asset Turnover 0.6 → ___
- Asset Turnover 0.35, D/E 2.5, ROE 12% → ___
Pair: Compare answers. Which single ratio was the strongest clue in each case?
Check your answers
1) IT Services · 2) Telecom · 3) Cement/Steel · 4) Power/Utilities. Clues: 1) high ROE+low D/E · 2) high debt+high margin · 3) low turnover+moderate debt · 4) very low turnover+high debt.
Hands-On Project: Sector Ratio Diagnostic Report
You are an equity research analyst covering one of the following Indian sectors: IT Services, Consumer Staples, or Pharmaceuticals. Your task is to produce a ratio diagnostic report comparing the top 4–5 companies in your chosen sector and identifying the strongest and weakest operators based on the numbers.
Excel Option Complete the identical project in Excel: extend your Session 3 workbook with the ratio rows from this session's Excel Path boxes (liquidity, solvency, DuPont, CCC), roll all companies into the Summary sheet with Peer Median columns, apply the conditional-formatting traffic lights (ICR red < 1, CCC green < 0), and build the same three charts by selection (margin bars, ROE-vs-D/E scatter, CCC bars). Your deliverable is the workbook; the analysis, outlier identification, and 300-word report are identical requirements. Validation shortcut: your computed OPM, ROE, and D/E should match Screener.in's printed ratios — if they do, your formulas are correct.
Steps
- Choose your sector and identify 4–5 listed companies using the
.NSsuffix. (Excel track: one worksheet per company, data pasted from Screener.in.) - Run the
compare_ratios()function from Section 4.6 for your chosen tickers. (Excel track: build the ratio rows + Summary sheet per Section 4.6's Excel Path.) - Create visualizations (minimum 3):
- A grouped bar chart comparing Operating Margin, Net Margin, and ROE across companies
- A scatter plot of ROE vs Debt-to-Equity (label each company) — this reveals whether high ROE is from operations or leverage
- A bar chart of the Cash Conversion Cycle for each company — identify working capital leaders and laggards
- Identify outliers: For each ratio, identify the best and worst performer. Investigate: is the outlier a genuine competitive advantage/disadvantage, or a temporary distortion?
- Write a 300-word diagnostic report covering:
- Which company has the strongest overall financial profile? Justify with specific ratios.
- Which company has the weakest profile? Is it distressed or just differently positioned?
- One ratio that surprised you and what it implies for valuation.
- Analyst Showcase (NEW): In groups of 4, each person presents their ROE vs D/E scatter plot and defends the strongest and weakest company choice in 2 minutes. The group selects the most convincing diagnosis — and the class hears the top 2.
View Solution / Walkthrough
Sector Diagnostic: Indian IT Services (Representative Output)
import matplotlib.pyplot as plt
import seaborn as sns
it_tickers = ['TCS.NS', 'INFY.NS', 'WIPRO.NS', 'HCLTECH.NS', 'TECHM.NS']
it_labels = ['TCS', 'Infosys', 'Wipro', 'HCL Tech', 'Tech Mahindra']
it_comparison = compare_ratios(it_tickers, it_labels)
# --- Chart 1: Profitability Bar Chart ---
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
profit_metrics = ['Operating Margin (%)', 'Net Margin (%)', 'ROE (%)']
profit_data = it_comparison.loc[it_comparison.index.intersection(profit_metrics)]
profit_data.T.plot(kind='bar', ax=axes[0], color=['#6c8cff', '#00c9a7', '#f0a040'])
axes[0].set_title('Profitability Comparison — IT Services')
axes[0].set_ylabel('Percent (%)')
axes[0].legend(fontsize=8)
axes[0].tick_params(axis='x', rotation=30)
# --- Chart 2: ROE vs Debt-to-Equity Scatter ---
ax = axes[1]
if 'ROE (%)' in it_comparison.index and 'Debt-to-Equity' in it_comparison.index:
x = it_comparison.loc['Debt-to-Equity']
y = it_comparison.loc['ROE (%)']
ax.scatter(x, y, s=200, c='#6c8cff', edgecolors='white', linewidth=2, zorder=5)
for i, name in enumerate(it_comparison.columns):
ax.annotate(name, (x.iloc[i], y.iloc[i]),
textcoords="offset points", xytext=(8, 5), fontsize=9)
ax.set_xlabel('Debt-to-Equity')
ax.set_ylabel('ROE (%)')
ax.set_title('ROE vs Leverage — Does Debt Drive Returns?')
ax.axhline(y=it_comparison.loc['ROE (%)'].mean(), color='gray',
linestyle='--', alpha=0.5, label='Average ROE')
ax.legend(fontsize=8)
# --- Chart 3: Cash Conversion Cycle ---
ax = axes[2]
ccc_data = it_comparison.loc[it_comparison.index.intersection(['Cash Conversion Cycle'])]
if not ccc_data.empty:
colors = ['#00c9a7' if v < 0 else '#f0a040' if v < 30 else '#e0556a'
for v in ccc_data.iloc[0]]
ax.bar(ccc_data.columns, ccc_data.iloc[0], color=colors)
ax.set_title('Cash Conversion Cycle (Days)')
ax.set_ylabel('Days')
ax.axhline(y=0, color='green', linestyle='--', alpha=0.5, label='CCC = 0')
ax.legend(fontsize=8)
ax.tick_params(axis='x', rotation=30)
plt.suptitle('Indian IT Services — Ratio Diagnostic Report',
fontsize=14, fontweight='bold', y=1.02)
plt.tight_layout()
plt.show()
Sample Diagnostic Report (300 words):
TCS demonstrates the strongest overall financial profile among Indian IT services companies. Its operating margin (~26%) leads the peer group, reflecting superior execution, scale advantages, and a higher proportion of high-value digital transformation engagements. Combined with negligible debt (D/E < 0.05), TCS's 45%+ ROE is almost entirely operationally driven, not levered — the highest-quality return profile possible. The negative cash conversion cycle signals that TCS collects from customers before paying its own obligations, effectively running on negative working capital.
Tech Mahindra shows the weakest profitability profile with an operating margin of ~12%, roughly half of TCS's. This is partly structural — Tech Mahindra has higher exposure to lower-margin telecom verticals and communication services. However, the gap also reflects operational efficiency differences. Its ROE of ~15% is respectable but, combined with slightly higher leverage, suggests a less durable return profile.
The most surprising finding is the working capital divergence. While TCS and Infosys maintain tight receivable management (DSO ~60 days), Tech Mahindra's DSO exceeds 90 days — implying either looser client payment terms or slower collection efforts. For a DCF valuation, this translates to higher working capital investment for every rupee of revenue growth, directly reducing free cash flow. An analyst forecasting identical revenue growth for TCS and Tech Mahindra would miss a critical difference in cash generation. This is exactly why ratio analysis must precede forecasting.
Key Takeaways
Four ratio families, four questions: Liquidity = survival, Solvency = sustainability, Profitability = earnings power, Efficiency = management quality. All four must be assessed before you value a company.
The DuPont decomposition reveals whether high ROE comes from operational excellence (high margins, high turnover) or financial engineering (high leverage). The former is sustainable; the latter is fragile.
The Cash Conversion Cycle directly impacts free cash flow. Two companies with identical margins can have materially different valuations if one ties up far more capital in working capital.
Python automation turns hours of manual ratio computation into seconds. The pipeline from Section 4.6 is the foundation you will reuse for every company you value in this course.
Ratios are diagnostic tools, not answers. A ratio tells you what to investigate, not why. Always trace anomalies back to the underlying financial statement line items.
Test Your Understanding
1. A company has a Current Ratio of 1.2 and a Quick Ratio of 0.4. What does the large gap between these two ratios most likely indicate?
2. The DuPont decomposition expresses ROE as the product of three drivers. Which of the following is the correct formula?
3. Which of the following patterns in financial ratios would raise the strongest concern about potential accounting manipulation?
4. A company has an Interest Coverage Ratio of 0.8. What does this mean?
5. Why is a negative Cash Conversion Cycle (CCC) considered desirable for a business?
Which ratio family do you now understand best — and which still feels fuzzy? Write one sentence for each. In Session 5 we build on ROIC, and you will use these ratios to judge which companies actually create value.