Unit 1 Mini-ProjectΒΆ
- Class: 1:55
- Analyst: Theo Brito Machado, Olivia Smith
- Company: Ralph Lauren (RL)
1) Importing data from the CSVΒΆ
Here we are importing the data from the CSV we created on the first notebook, path is in the root folder named data. For code quality purpose I am also importing all the libraries and function on the first line of the notebook
# As mentioned, keeping the good practise of doing all the importations on top of the code
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pathlib import Path
from IPython.display import display
pd.set_option("display.max_columns", 200)
pd.set_option("display.width", 180)
pd.set_option("display.float_format", lambda x: f"{x:,.2f}")
# same we will always use to find root
def find_project_root(start: Path | None = None) -> Path:
start_path = (start or Path.cwd()).resolve()
for candidate in [start_path, *start_path.parents]:
if (candidate / "DATA" / "csv_export").exists() and (candidate / "Mini-projects").exists():
return candidate
raise FileNotFoundError("Could not find the project root containing DATA/csv_export and Mini-projects.")
# Function for reading the csv
def load_source_csv(csv_dir: Path, filename: str) -> pd.DataFrame:
path = csv_dir / filename
header = pd.read_csv(path, nrows=0)
parse_dates = [c for c in ["date", "acceptedDate", "filingDate"] if c in header.columns]
return pd.read_csv(path, parse_dates=parse_dates)
# use the root function + actual data directions
PROJECT_ROOT = find_project_root()
CSV_DIR = PROJECT_ROOT / "DATA" / "csv_export"
OUT_DIR = PROJECT_ROOT / "Mini-projects" / "outputs"
QUARTERLY_EXPORT_DIR = CSV_DIR
# Loading csv
is_raw = load_source_csv(CSV_DIR, "RL_income_statement.csv")
bs_raw = load_source_csv(CSV_DIR, "RL_balance_sheet.csv")
cf_raw = load_source_csv(CSV_DIR, "RL_cash_flow.csv")
ratios_raw = load_source_csv(CSV_DIR, "RL_ratios.csv")
# Printing the shape information, since we have 40 quarters of data should always be (40,n)
print("Project root:", PROJECT_ROOT)
print("Loaded tables:")
print(" Income statement:", is_raw.shape)
print(" Balance sheet :", bs_raw.shape)
print(" Cash flow :", cf_raw.shape)
print(" Ratios :", ratios_raw.shape)
Project root: /Users/theomachado/Library/CloudStorage/OneDrive-UniversityofFlorida/Spring 2026/FIN4453 - OFC Loaded tables: Income statement: (40, 39) Balance sheet : (40, 61) Cash flow : (40, 47) Ratios : (40, 64)
2) Build a TidyΒΆ
- Timeline: fiscal quarters, with each Q1-Q4.
- Units: USD MM
- Audit checks:
- one source row per fiscal quarter for IS, BS, CF, and ratios
Total Assets = Total Liabilities + Total EquityGross Profit = Revenue - COGS
- Professor note: Dr. Mascio, as mentioned in the executive summary RL report a bit different, they have a time difference. Example: 2021Q1 is the quarter ended actually in june 2020.
#Facilitating the order of the quarters process
PERIOD_ORDER = {"Q1": 1, "Q2": 2, "Q3": 3, "Q4": 4}
def make_fiscal_label(fiscal_year: int | float, period: str) -> str:
return f"FY{int(fiscal_year)} {period}"
#preping the statments
def _prep(df: pd.DataFrame) -> pd.DataFrame:
out = df.copy()
for col in ["date", "acceptedDate", "filingDate"]:
if col in out.columns:
out[col] = pd.to_datetime(out[col], errors="coerce")
out["fiscalYear"] = pd.to_numeric(out["fiscalYear"], errors="coerce")
out = out.dropna(subset=["fiscalYear", "period"]).copy()
out["fiscalYear"] = out["fiscalYear"].astype(int)
out["period"] = out["period"].astype(str).str.upper().str.strip()
out = out[out["period"].isin(PERIOD_ORDER)].copy()
out["QuarterNumber"] = out["period"].map(PERIOD_ORDER).astype(int)
sort_cols = [c for c in ["date", "acceptedDate", "filingDate", "fiscalYear", "QuarterNumber"] if c in out.columns]
return out.sort_values(sort_cols).reset_index(drop=True)
# For purpose of cleaning the data and avoiding mechanical mistakes we clean and remove all the duplicated rows
def _latest_unique_rows(df: pd.DataFrame, keys: list[str]) -> pd.DataFrame:
sort_cols = [c for c in [*keys, "acceptedDate", "filingDate", "date"] if c in df.columns]
ordered = df.sort_values(sort_cols) if sort_cols else df.copy()
return ordered.drop_duplicates(subset=keys, keep="last").copy()
def _quarter_record_counts(df: pd.DataFrame) -> pd.Series:
counts = df.groupby(["fiscalYear", "period"]).size()
counts.index = [make_fiscal_label(year, period) for year, period in counts.index]
return counts.sort_index()
# Creating a pd dataframe for each statment and making the df colum names to later bring the raw data in
def build_tidy_pack(
is_df: pd.DataFrame,
bs_df: pd.DataFrame,
cf_df: pd.DataFrame,
ratios_df: pd.DataFrame,
):
merge_keys = ["date", "fiscalYear", "period"]
is_p = _latest_unique_rows(_prep(is_df), merge_keys)
bs_p = _latest_unique_rows(_prep(bs_df), merge_keys)
cf_p = _latest_unique_rows(_prep(cf_df), merge_keys)
r_p = _latest_unique_rows(_prep(ratios_df), merge_keys)
income_cols = [
"date", "fiscalYear", "period",
"revenue", "costOfRevenue", "grossProfit", "operatingExpenses", "operatingIncome",
"ebit", "ebitda", "depreciationAndAmortization", "interestExpense",
"incomeBeforeTax", "incomeTaxExpense", "netIncome",
]
balance_cols = [
"date", "fiscalYear", "period",
"cashAndCashEquivalents", "cashAndShortTermInvestments",
"accountsReceivables", "inventory", "accountPayables", "accruedExpenses",
"deferredRevenue", "otherCurrentAssets", "otherCurrentLiabilities",
"totalCurrentAssets", "totalCurrentLiabilities",
"propertyPlantEquipmentNet", "totalAssets", "totalLiabilities", "totalEquity",
"totalStockholdersEquity", "totalDebt", "netDebt",
]
cash_flow_cols = [
"date", "fiscalYear", "period",
"operatingCashFlow", "capitalExpenditure", "freeCashFlow", "changeInWorkingCapital",
"deferredIncomeTax", "stockBasedCompensation", "commonDividendsPaid", "incomeTaxesPaid",
]
ratio_cols = [
"date", "fiscalYear", "period",
"priceToEarningsRatio", "priceToSalesRatio", "priceToBookRatio", "enterpriseValueMultiple", "debtToMarketCap",
]
income = is_p[[c for c in income_cols if c in is_p.columns]].rename(columns={
"revenue": "Revenue",
"costOfRevenue": "COGS",
"grossProfit": "GrossProfit",
"operatingExpenses": "OperatingExpenses",
"operatingIncome": "OperatingIncome",
"ebit": "EBIT",
"ebitda": "EBITDA",
"depreciationAndAmortization": "DA",
"interestExpense": "InterestExpense",
"incomeBeforeTax": "EBT",
"incomeTaxExpense": "TaxExpense",
"netIncome": "NetIncome",
})
balance = bs_p[[c for c in balance_cols if c in bs_p.columns]].rename(columns={
"cashAndCashEquivalents": "Cash",
"cashAndShortTermInvestments": "CashAndSTInvestments",
"accountsReceivables": "AccountsReceivable",
"inventory": "Inventory",
"accountPayables": "AccountsPayable",
"accruedExpenses": "AccruedExpenses",
"deferredRevenue": "DeferredRevenue",
"otherCurrentAssets": "OtherCurrentAssets",
"otherCurrentLiabilities": "OtherCurrentLiabilities",
"totalCurrentAssets": "TotalCurrentAssets",
"totalCurrentLiabilities": "TotalCurrentLiabilities",
"propertyPlantEquipmentNet": "PPENet",
"totalAssets": "TotalAssets",
"totalLiabilities": "TotalLiabilities",
"totalEquity": "TotalEquity",
"totalStockholdersEquity": "TotalStockholdersEquity",
"totalDebt": "TotalDebt",
"netDebt": "NetDebt",
})
cash_flow = cf_p[[c for c in cash_flow_cols if c in cf_p.columns]].rename(columns={
"operatingCashFlow": "OperatingCashFlow",
"capitalExpenditure": "CapexRaw",
"freeCashFlow": "FreeCashFlow",
"changeInWorkingCapital": "DeltaWC_CFSign",
"deferredIncomeTax": "DeferredTax",
"stockBasedCompensation": "SBC",
"commonDividendsPaid": "DividendsRaw",
"incomeTaxesPaid": "CashTaxesPaid",
})
ratios = r_p[[c for c in ratio_cols if c in r_p.columns]].rename(columns={
"priceToEarningsRatio": "PE",
"priceToSalesRatio": "PS",
"priceToBookRatio": "PB",
"enterpriseValueMultiple": "EV_EBITDA",
"debtToMarketCap": "DebtToMarketCap",
})
tidy = (
income.merge(balance, on=merge_keys, how="inner", validate="1:1")
.merge(cash_flow, on=merge_keys, how="inner", validate="1:1")
.merge(ratios, on=merge_keys, how="left", validate="1:1")
)
# we are creating cleaner time colums making sure the raw data will align perfectly
tidy["Date"] = pd.to_datetime(tidy["date"])
tidy["FiscalYear"] = tidy["fiscalYear"].astype(int)
tidy["Period"] = tidy["period"].astype(str)
tidy["QuarterNumber"] = tidy["Period"].map(PERIOD_ORDER).astype(int)
tidy["Quarter"] = [make_fiscal_label(year, period) for year, period in zip(tidy["FiscalYear"], tidy["Period"])]
# correct indexing the tidy df by quarters
tidy = tidy.sort_values(["Date", "FiscalYear", "QuarterNumber"]).set_index("Quarter")
tidy.index.name = "Quarter"
tidy = tidy.drop(columns=[c for c in ["date", "fiscalYear", "period"] if c in tidy.columns])
#fixing sign presentations to use easier in graphs
if "CapexRaw" in tidy.columns:
tidy["CapEx"] = tidy["CapexRaw"].abs()
if "DividendsRaw" in tidy.columns:
tidy["Dividends"] = tidy["DividendsRaw"].abs()
if "TotalStockholdersEquity" in tidy.columns:
tidy["EquityForBalance"] = tidy["TotalStockholdersEquity"]
elif "TotalEquity" in tidy.columns:
tidy["EquityForBalance"] = tidy["TotalEquity"]
if "NetDebt" not in tidy.columns and {"TotalDebt", "Cash"}.issubset(tidy.columns):
tidy["NetDebt"] = tidy["TotalDebt"] - tidy["Cash"]
# This is key, we want to use MM data, however if we divide everything my 1,000,000 ratios would also be divided and become non-sense. Thefore, we are separating the ratio cols from the numeric ones and than doing the mm division.
ratio_like_cols = {"PE", "PS", "PB", "EV_EBITDA", "DebtToMarketCap"}
id_like_cols = {"FiscalYear", "QuarterNumber"}
numeric_cols = tidy.select_dtypes(include="number").columns
currency_cols = [c for c in numeric_cols if c not in ratio_like_cols | id_like_cols]
tidy[currency_cols] = tidy[currency_cols] / 1_000_000
ttm_flow_cols = [
"Revenue", "COGS", "GrossProfit", "OperatingIncome", "EBIT", "EBITDA",
"InterestExpense", "EBT", "TaxExpense", "NetIncome", "OperatingCashFlow",
"FreeCashFlow", "CapEx", "Dividends",
]
for col in ttm_flow_cols:
if col in tidy.columns:
tidy[f"{col}_TTM"] = tidy[col].rolling(4, min_periods=4).sum()
for stock_col, avg_col in [
("TotalAssets", "AvgAssets"),
("EquityForBalance", "AvgEquity"),
("AccountsReceivable", "AvgAR"),
("Inventory", "AvgInventory"),
("AccountsPayable", "AvgAP"),
]:
if stock_col in tidy.columns:
tidy[avg_col] = (tidy[stock_col] + tidy[stock_col].shift(1)) / 2
# To avoid mistakes and as since was required auditable dataset we are ensuring that only one colums of this source table contributes to that period. Example, only one row of the IS is going to contribute to the 2017Q1
audit = pd.DataFrame(index=tidy.index)
audit["IS_RecordCount"] = _quarter_record_counts(_prep(is_df)).reindex(tidy.index).fillna(0).astype(int)
audit["BS_RecordCount"] = _quarter_record_counts(_prep(bs_df)).reindex(tidy.index).fillna(0).astype(int)
audit["CF_RecordCount"] = _quarter_record_counts(_prep(cf_df)).reindex(tidy.index).fillna(0).astype(int)
audit["Ratios_RecordCount"] = _quarter_record_counts(_prep(ratios_df)).reindex(tidy.index).fillna(0).astype(int)
if {"TotalAssets", "TotalLiabilities", "EquityForBalance"}.issubset(tidy.columns):
audit["BalanceError_USDm"] = tidy["TotalAssets"] - (tidy["TotalLiabilities"] + tidy["EquityForBalance"])
else:
audit["BalanceError_USDm"] = np.nan
if {"GrossProfit", "Revenue", "COGS"}.issubset(tidy.columns):
audit["GrossProfitError_USDm"] = tidy["GrossProfit"] - (tidy["Revenue"] - tidy["COGS"])
else:
audit["GrossProfitError_USDm"] = np.nan
audit["AuditPass"] = (
audit[["IS_RecordCount", "BS_RecordCount", "CF_RecordCount", "Ratios_RecordCount"]].eq(1).all(axis=1)
& audit["BalanceError_USDm"].fillna(0).abs().lt(0.5)
& audit["GrossProfitError_USDm"].fillna(0).abs().lt(0.5)
)
return tidy, audit
# Using what we just created and bringing in the raw data
tidy, audit = build_tidy_pack(is_raw, bs_raw, cf_raw, ratios_raw)
print("Fiscal quarters in tidy dataset:", tidy.index[0], "to", tidy.index[-1])
print("Rows x columns:", tidy.shape)
display(tidy.head(8))
display(audit.head(8))
# Wasn't required but for control purpose we are going to save a tidy CSV that can be loaded to other projects data that is already ready to go.
out_dir = OUT_DIR
out_dir.mkdir(parents=True, exist_ok=True)
tidy.to_csv(out_dir / "unit1_tidy_historical_pack_usdm.csv")
audit.to_csv(out_dir / "unit1_tidy_historical_audit.csv")
Fiscal quarters in tidy dataset: FY2016 Q4 to FY2026 Q3 Rows x columns: (40, 69)
| Revenue | COGS | GrossProfit | OperatingExpenses | OperatingIncome | EBIT | EBITDA | DA | InterestExpense | EBT | TaxExpense | NetIncome | Cash | CashAndSTInvestments | AccountsReceivable | Inventory | AccountsPayable | AccruedExpenses | DeferredRevenue | OtherCurrentAssets | OtherCurrentLiabilities | TotalCurrentAssets | TotalCurrentLiabilities | PPENet | TotalAssets | TotalLiabilities | TotalEquity | TotalStockholdersEquity | TotalDebt | NetDebt | OperatingCashFlow | CapexRaw | FreeCashFlow | DeltaWC_CFSign | DeferredTax | SBC | DividendsRaw | CashTaxesPaid | PE | PS | PB | EV_EBITDA | DebtToMarketCap | Date | FiscalYear | Period | QuarterNumber | CapEx | Dividends | EquityForBalance | Revenue_TTM | COGS_TTM | GrossProfit_TTM | OperatingIncome_TTM | EBIT_TTM | EBITDA_TTM | InterestExpense_TTM | EBT_TTM | TaxExpense_TTM | NetIncome_TTM | OperatingCashFlow_TTM | FreeCashFlow_TTM | CapEx_TTM | Dividends_TTM | AvgAssets | AvgEquity | AvgAR | AvgInventory | AvgAP | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Quarter | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FY2016 Q4 | 1,871.00 | 857.00 | 1,014.00 | 947.00 | 67.00 | 69.00 | 152.00 | 7.00 | 7.00 | 62.00 | 21.00 | 41.00 | 456.30 | 1,085.70 | 516.50 | 1,125.00 | 151.00 | 581.20 | 50.10 | 90.20 | 66.20 | 3,053.00 | 1,198.00 | 1,583.00 | 6,213.00 | 2,469.00 | 3,744.00 | 3,744.00 | 999.50 | 543.20 | 155.00 | -93.00 | 62.00 | -153.00 | -4.00 | 18.00 | -42.00 | 0.00 | 49.76 | 4.36 | 2.18 | 57.26 | 0.09 | 2016-04-02 | 2016 | Q4 | 4 | 93.00 | 42.00 | 3,744.00 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| FY2017 Q1 | 1,552.00 | 657.60 | 894.60 | 925.80 | -31.20 | -29.80 | 48.60 | 6.00 | 3.40 | -33.00 | -10.90 | -22.00 | 457.00 | 1,076.00 | 338.00 | 1,242.00 | 192.00 | 591.00 | 48.00 | 97.00 | 134.00 | 3,002.00 | 1,296.00 | 1,565.00 | 6,118.00 | 2,552.00 | 3,566.00 | 3,566.00 | 978.00 | 521.00 | 243.00 | -78.00 | 165.00 | 81.00 | 3.00 | 18.00 | -41.40 | 0.00 | -84.54 | 4.79 | 2.09 | 163.81 | 0.09 | 2016-07-02 | 2017 | Q1 | 1 | 78.00 | 41.40 | 3,566.00 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 6,165.50 | 3,655.00 | 427.25 | 1,183.50 | 171.50 |
| FY2017 Q2 | 1,821.00 | 866.40 | 954.20 | 877.90 | 76.30 | 77.80 | 153.40 | 76.00 | 4.10 | 73.00 | 28.00 | 45.00 | 434.00 | 965.00 | 490.00 | 1,173.00 | 159.00 | 565.00 | 38.00 | 81.00 | 127.00 | 2,976.00 | 1,217.00 | 1,564.00 | 6,067.00 | 2,469.00 | 3,598.00 | 3,598.00 | 973.00 | 539.00 | -11.00 | -87.00 | -98.00 | -244.00 | -12.00 | 14.00 | -41.00 | 0.00 | 46.47 | 4.59 | 2.32 | 58.04 | 0.08 | 2016-10-01 | 2017 | Q2 | 2 | 87.00 | 41.00 | 3,598.00 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 6,092.50 | 3,582.00 | 414.00 | 1,207.50 | 175.50 |
| FY2017 Q3 | 1,714.00 | 731.40 | 983.20 | 854.90 | 128.30 | 126.70 | 204.60 | 6.00 | 3.60 | 124.00 | 41.80 | 82.00 | 928.00 | 1,381.00 | 285.00 | 984.00 | 158.00 | 609.00 | 34.00 | 130.00 | 94.00 | 3,034.00 | 1,151.00 | 1,514.00 | 5,966.00 | 2,356.00 | 3,610.00 | 3,610.00 | 863.00 | -65.00 | 618.00 | -60.00 | 558.00 | 391.00 | 19.00 | 14.00 | -42.00 | 0.00 | 22.75 | 4.35 | 2.07 | 36.15 | 0.08 | 2016-12-31 | 2017 | Q3 | 3 | 60.00 | 42.00 | 3,610.00 | 6,958.00 | 3,112.40 | 3,846.00 | 240.40 | 243.70 | 558.60 | 18.10 | 226.00 | 79.90 | 146.00 | 1,005.00 | 687.00 | 318.00 | 166.40 | 6,016.50 | 3,604.00 | 387.50 | 1,078.50 | 158.50 |
| FY2017 Q4 | 1,565.80 | 746.70 | 819.10 | 1,086.90 | -267.80 | -267.50 | -192.00 | 75.50 | 1.40 | -268.90 | -64.60 | -204.30 | 668.30 | 1,353.00 | 450.20 | 791.50 | 147.70 | 564.60 | 29.70 | 82.30 | 153.10 | 2,954.50 | 1,159.90 | 1,316.00 | 5,652.00 | 2,352.40 | 3,299.60 | 3,299.60 | 861.70 | 193.40 | 102.30 | -59.00 | 43.30 | 5.60 | -48.90 | 17.60 | -40.80 | 0.00 | -8.20 | 4.28 | 2.03 | -35.91 | 0.09 | 2017-04-01 | 2017 | Q4 | 4 | 59.00 | 40.80 | 3,299.60 | 6,652.80 | 3,002.10 | 3,651.10 | -94.40 | -92.80 | 214.60 | 12.50 | -104.90 | -5.70 | -99.30 | 952.30 | 668.30 | 284.00 | 165.20 | 5,809.00 | 3,454.80 | 367.60 | 887.75 | 152.85 |
| FY2018 Q1 | 1,347.10 | 495.90 | 851.20 | 760.90 | 90.30 | 91.80 | 164.70 | 72.90 | 5.00 | 86.80 | 27.30 | 59.50 | 830.40 | 1,570.90 | 279.20 | 859.90 | 160.90 | 587.40 | 30.60 | 90.30 | 134.90 | 3,086.70 | 1,216.90 | 1,273.30 | 5,814.00 | 2,453.90 | 3,360.10 | 3,360.10 | 859.20 | 28.80 | 334.20 | -41.90 | 292.30 | 182.50 | -14.70 | 21.60 | -40.50 | 0.00 | 25.30 | 4.47 | 1.79 | 36.74 | 0.10 | 2017-07-01 | 2018 | Q1 | 1 | 41.90 | 40.50 | 3,360.10 | 6,447.90 | 2,840.40 | 3,607.70 | 27.10 | 28.80 | 330.70 | 14.10 | 14.90 | 32.50 | -17.80 | 1,043.50 | 795.60 | 247.90 | 164.30 | 5,733.00 | 3,329.85 | 364.70 | 825.70 | 154.30 |
| FY2018 Q2 | 1,664.20 | 668.40 | 995.80 | 802.50 | 193.30 | 195.80 | 269.60 | 73.80 | 4.60 | 191.20 | 47.40 | 143.80 | 1,111.60 | 1,618.70 | 470.30 | 864.60 | 172.80 | 610.10 | 33.10 | 81.80 | 143.60 | 3,324.40 | 1,590.10 | 1,240.50 | 6,028.30 | 2,518.70 | 3,509.60 | 3,509.60 | 855.10 | -256.50 | 102.80 | -32.80 | 70.00 | -136.20 | -10.60 | 17.80 | -40.60 | 0.00 | 12.54 | 4.33 | 2.06 | 25.80 | 0.08 | 2017-09-30 | 2018 | Q2 | 2 | 32.80 | 40.60 | 3,509.60 | 6,291.10 | 2,642.40 | 3,649.30 | 144.10 | 146.80 | 446.90 | 14.60 | 133.10 | 51.90 | 81.00 | 1,157.30 | 963.60 | 193.70 | 163.90 | 5,921.15 | 3,434.85 | 374.75 | 862.25 | 166.85 |
| FY2018 Q3 | 1,641.80 | 645.60 | 996.20 | 807.00 | 189.20 | 191.10 | 263.80 | 72.70 | 4.80 | 186.30 | 268.10 | -81.80 | 1,175.70 | 2,038.00 | 295.20 | 825.40 | 184.30 | 659.60 | 33.90 | 73.70 | 130.50 | 3,533.20 | 1,710.20 | 1,215.90 | 6,199.00 | 2,791.50 | 3,407.50 | 3,407.50 | 849.00 | -326.70 | 514.10 | -48.30 | 465.80 | 479.20 | 17.30 | 16.90 | -40.60 | 0.00 | -25.89 | 5.16 | 2.49 | 30.87 | 0.07 | 2017-12-30 | 2018 | Q3 | 3 | 48.30 | 40.60 | 3,407.50 | 6,218.90 | 2,556.60 | 3,662.30 | 205.00 | 211.20 | 506.10 | 15.80 | 195.40 | 278.20 | -82.80 | 1,053.40 | 871.40 | 182.00 | 162.50 | 6,113.65 | 3,458.55 | 382.75 | 845.00 | 178.55 |
| IS_RecordCount | BS_RecordCount | CF_RecordCount | Ratios_RecordCount | BalanceError_USDm | GrossProfitError_USDm | AuditPass | |
|---|---|---|---|---|---|---|---|
| Quarter | |||||||
| FY2016 Q4 | 1 | 1 | 1 | 1 | 0.00 | 0.00 | True |
| FY2017 Q1 | 1 | 1 | 1 | 1 | 0.00 | 0.20 | True |
| FY2017 Q2 | 1 | 1 | 1 | 1 | 0.00 | -0.40 | True |
| FY2017 Q3 | 1 | 1 | 1 | 1 | 0.00 | 0.60 | False |
| FY2017 Q4 | 1 | 1 | 1 | 1 | 0.00 | 0.00 | True |
| FY2018 Q1 | 1 | 1 | 1 | 1 | 0.00 | 0.00 | True |
| FY2018 Q2 | 1 | 1 | 1 | 1 | 0.00 | -0.00 | True |
| FY2018 Q3 | 1 | 1 | 1 | 1 | 0.00 | 0.00 | True |
3) Common-SizeΒΆ
Here we are going to analyse the historical data behavior. As mentioned in class, a great way to understand more of the company is visually identifying the disruptions and investigating the causes.
Also we are going to start using data as % of revenue to better visualization and control
#Defining the 2 main functions that we are going to use for conduct this analysis
def common_size(df: pd.DataFrame, base_col: str, cols: list[str]) -> pd.DataFrame:
base = pd.to_numeric(df[base_col], errors="coerce").replace(0, np.nan)
out = pd.DataFrame(index=df.index)
for c in cols:
if c in df.columns:
out[c] = pd.to_numeric(df[c], errors="coerce") / base
return out * 100.0
def add_trend_metrics(df: pd.DataFrame, cols: list[str]) -> pd.DataFrame:
out = df.copy()
for c in cols:
series = pd.to_numeric(out[c], errors="coerce")
out[c] = series
out[f"{c}_QoQ_pct"] = series.pct_change() * 100
out[f"{c}_YoY_pct"] = series.pct_change(4) * 100
out[f"{c}_TTM"] = series.rolling(4, min_periods=4).sum()
return out
# Settings columns with information that we are going to analyse
is_cs_cols = ["Revenue", "COGS", "GrossProfit", "OperatingExpenses", "OperatingIncome", "NetIncome"]
bs_cs_cols = ["Cash", "AccountsReceivable", "Inventory", "PPENet", "TotalDebt", "TotalLiabilities", "EquityForBalance", "TotalAssets"]
common_is = common_size(tidy, "Revenue", [c for c in is_cs_cols if c in tidy.columns])
common_bs = common_size(tidy, "TotalAssets", [c for c in bs_cs_cols if c in tidy.columns])
# Creating columns for the trending analyse
trend_cols = [c for c in ["Revenue", "GrossProfit", "OperatingIncome", "NetIncome", "OperatingCashFlow", "FreeCashFlow", "CapEx"] if c in tidy.columns]
trend = add_trend_metrics(tidy[trend_cols], trend_cols)
display(common_is.round(2))
display(common_bs.round(2))
display(trend.round(2))
fig, axes = plt.subplots(1, 2, figsize=(16, 5))
marg = pd.DataFrame(index=tidy.index)
if {"GrossProfit", "Revenue"}.issubset(tidy.columns):
marg["GrossMargin"] = tidy["GrossProfit"] / tidy["Revenue"].replace(0, np.nan) * 100
if {"OperatingIncome", "Revenue"}.issubset(tidy.columns):
marg["OperatingMargin"] = tidy["OperatingIncome"] / tidy["Revenue"].replace(0, np.nan) * 100
if {"NetIncome", "Revenue"}.issubset(tidy.columns):
marg["NetMargin"] = tidy["NetIncome"] / tidy["Revenue"].replace(0, np.nan) * 100
for col in marg.columns:
axes[0].plot(marg.index, marg[col], marker="o", linewidth=1.8, label=col)
axes[0].set_title("Quarterly Common-Size Margins (%)")
axes[0].set_xlabel("Fiscal Quarter")
axes[0].set_ylabel("Percent")
axes[0].tick_params(axis="x", rotation=45)
axes[0].legend()
#Formating for getting plot ready
plot_cols = [c for c in ["Revenue", "NetIncome"] if c in tidy.columns]
for col in plot_cols:
axes[1].plot(tidy.index, tidy[col], marker="o", linewidth=1.8, label=col)
axes[1].set_title("Quarterly Level Trends (USD m)")
axes[1].set_xlabel("Fiscal Quarter")
axes[1].set_ylabel("USD m")
axes[1].tick_params(axis="x", rotation=45)
axes[1].legend()
#plotting
plt.tight_layout()
plt.show()
| Revenue | COGS | GrossProfit | OperatingExpenses | OperatingIncome | NetIncome | |
|---|---|---|---|---|---|---|
| Quarter | ||||||
| FY2016 Q4 | 100.00 | 45.80 | 54.20 | 50.61 | 3.58 | 2.19 |
| FY2017 Q1 | 100.00 | 42.37 | 57.64 | 59.65 | -2.01 | -1.42 |
| FY2017 Q2 | 100.00 | 47.58 | 52.40 | 48.21 | 4.19 | 2.47 |
| FY2017 Q3 | 100.00 | 42.67 | 57.36 | 49.88 | 7.49 | 4.78 |
| FY2017 Q4 | 100.00 | 47.69 | 52.31 | 69.41 | -17.10 | -13.05 |
| FY2018 Q1 | 100.00 | 36.81 | 63.19 | 56.48 | 6.70 | 4.42 |
| FY2018 Q2 | 100.00 | 40.16 | 59.84 | 48.22 | 11.62 | 8.64 |
| FY2018 Q3 | 100.00 | 39.32 | 60.68 | 49.15 | 11.52 | -4.98 |
| FY2018 Q4 | 100.00 | 40.59 | 59.41 | 57.75 | 1.66 | 2.70 |
| FY2019 Q1 | 100.00 | 35.59 | 64.41 | 55.06 | 9.36 | 7.84 |
| FY2019 Q2 | 100.00 | 39.13 | 60.87 | 48.45 | 12.42 | 10.07 |
| FY2019 Q3 | 100.00 | 38.61 | 61.39 | 50.16 | 11.23 | 6.95 |
| FY2019 Q4 | 100.00 | 40.13 | 59.87 | 58.02 | 1.85 | 2.10 |
| FY2020 Q1 | 100.00 | 35.55 | 64.45 | 54.42 | 10.03 | 8.20 |
| FY2020 Q2 | 100.00 | 38.52 | 61.48 | 47.82 | 13.66 | 10.67 |
| FY2020 Q3 | 100.00 | 37.79 | 62.21 | 49.39 | 12.82 | 19.08 |
| FY2020 Q4 | 100.00 | 53.35 | 46.65 | 68.93 | -22.27 | -19.54 |
| FY2021 Q1 | 100.00 | 28.47 | 71.53 | 105.99 | -34.46 | -26.19 |
| FY2021 Q2 | 100.00 | 33.02 | 66.98 | 68.68 | -1.70 | -3.28 |
| FY2021 Q3 | 100.00 | 35.06 | 64.94 | 53.04 | 11.89 | 8.36 |
| FY2021 Q4 | 100.00 | 39.17 | 60.83 | 62.83 | -2.00 | -5.76 |
| FY2022 Q1 | 100.00 | 29.66 | 70.34 | 54.31 | 16.03 | 11.97 |
| FY2022 Q2 | 100.00 | 32.50 | 67.50 | 50.75 | 16.75 | 12.85 |
| FY2022 Q3 | 100.00 | 34.00 | 66.00 | 50.07 | 15.92 | 11.99 |
| FY2022 Q4 | 100.00 | 36.55 | 63.45 | 61.03 | 2.42 | 1.60 |
| FY2023 Q1 | 100.00 | 32.82 | 67.18 | 55.43 | 11.75 | 8.28 |
| FY2023 Q2 | 100.00 | 35.24 | 64.76 | 51.67 | 13.08 | 9.53 |
| FY2023 Q3 | 100.00 | 35.02 | 64.98 | 49.59 | 15.40 | 11.82 |
| FY2023 Q4 | 100.00 | 38.30 | 61.70 | 59.09 | 2.61 | 2.10 |
| FY2024 Q1 | 100.00 | 31.04 | 68.96 | 57.84 | 11.12 | 8.83 |
| FY2024 Q2 | 100.00 | 34.47 | 65.53 | 55.46 | 10.07 | 9.00 |
| FY2024 Q3 | 100.00 | 33.51 | 66.49 | 50.07 | 16.43 | 14.30 |
| FY2024 Q4 | 100.00 | 33.43 | 66.57 | 59.69 | 6.88 | 5.78 |
| FY2025 Q1 | 100.00 | 29.52 | 70.48 | 56.69 | 13.79 | 11.15 |
| FY2025 Q2 | 100.00 | 33.04 | 66.96 | 56.59 | 10.37 | 8.57 |
| FY2025 Q3 | 100.00 | 31.60 | 68.40 | 50.22 | 18.18 | 13.87 |
| FY2025 Q4 | 100.00 | 31.34 | 68.66 | 59.52 | 9.13 | 7.60 |
| FY2026 Q1 | 100.00 | 27.74 | 72.26 | 56.35 | 15.92 | 12.82 |
| FY2026 Q2 | 100.00 | 32.04 | 67.96 | 55.74 | 12.22 | 10.32 |
| FY2026 Q3 | 100.00 | 30.10 | 69.90 | 48.99 | 20.91 | 15.03 |
| Cash | AccountsReceivable | Inventory | PPENet | TotalDebt | TotalLiabilities | EquityForBalance | TotalAssets | |
|---|---|---|---|---|---|---|---|---|
| Quarter | ||||||||
| FY2016 Q4 | 7.34 | 8.31 | 18.11 | 25.48 | 16.09 | 39.74 | 60.26 | 100.00 |
| FY2017 Q1 | 7.47 | 5.52 | 20.30 | 25.58 | 15.99 | 41.71 | 58.29 | 100.00 |
| FY2017 Q2 | 7.15 | 8.08 | 19.33 | 25.78 | 16.04 | 40.70 | 59.30 | 100.00 |
| FY2017 Q3 | 15.55 | 4.78 | 16.49 | 25.38 | 14.47 | 39.49 | 60.51 | 100.00 |
| FY2017 Q4 | 11.82 | 7.97 | 14.00 | 23.28 | 15.25 | 41.62 | 58.38 | 100.00 |
| FY2018 Q1 | 14.28 | 4.80 | 14.79 | 21.90 | 14.78 | 42.21 | 57.79 | 100.00 |
| FY2018 Q2 | 18.44 | 7.80 | 14.34 | 20.58 | 14.18 | 41.78 | 58.22 | 100.00 |
| FY2018 Q3 | 18.97 | 4.76 | 13.32 | 19.61 | 13.70 | 45.03 | 54.97 | 100.00 |
| FY2018 Q4 | 21.24 | 6.86 | 12.39 | 19.31 | 13.87 | 43.72 | 56.28 | 100.00 |
| FY2019 Q1 | 8.82 | 4.31 | 14.75 | 18.92 | 13.85 | 43.32 | 56.68 | 100.00 |
| FY2019 Q2 | 8.99 | 7.02 | 16.16 | 18.37 | 15.08 | 43.76 | 56.24 | 100.00 |
| FY2019 Q3 | 11.17 | 4.99 | 15.01 | 17.72 | 15.21 | 44.95 | 55.05 | 100.00 |
| FY2019 Q4 | 9.83 | 6.70 | 13.76 | 17.49 | 15.55 | 44.69 | 55.31 | 100.00 |
| FY2020 Q1 | 8.83 | 3.96 | 13.46 | 32.72 | 36.50 | 58.97 | 41.03 | 100.00 |
| FY2020 Q2 | 7.58 | 6.68 | 14.01 | 35.67 | 39.08 | 59.69 | 40.31 | 100.00 |
| FY2020 Q3 | 14.50 | 4.69 | 12.15 | 34.94 | 37.88 | 58.14 | 41.86 | 100.00 |
| FY2020 Q4 | 22.26 | 3.81 | 10.11 | 34.22 | 44.33 | 63.01 | 36.99 | 100.00 |
| FY2021 Q1 | 31.67 | 1.40 | 9.99 | 31.13 | 51.18 | 66.98 | 33.02 | 100.00 |
| FY2021 Q2 | 25.96 | 4.57 | 11.44 | 30.03 | 46.54 | 67.18 | 32.82 | 100.00 |
| FY2021 Q3 | 32.08 | 4.57 | 10.60 | 29.69 | 45.37 | 67.06 | 32.94 | 100.00 |
| FY2021 Q4 | 32.70 | 5.72 | 9.62 | 28.57 | 45.90 | 66.98 | 33.02 | 100.00 |
| FY2022 Q1 | 32.61 | 4.61 | 10.09 | 27.08 | 44.37 | 65.87 | 34.13 | 100.00 |
| FY2022 Q2 | 29.20 | 5.13 | 11.35 | 25.93 | 42.64 | 64.99 | 35.01 | 100.00 |
| FY2022 Q3 | 27.99 | 5.05 | 11.42 | 25.78 | 42.27 | 66.53 | 33.47 | 100.00 |
| FY2022 Q4 | 24.13 | 5.25 | 12.65 | 26.94 | 43.91 | 67.17 | 32.83 | 100.00 |
| FY2023 Q1 | 20.96 | 5.04 | 16.95 | 28.57 | 40.43 | 65.99 | 34.01 | 100.00 |
| FY2023 Q2 | 16.44 | 7.27 | 18.73 | 28.45 | 41.02 | 66.50 | 33.50 | 100.00 |
| FY2023 Q3 | 22.25 | 6.02 | 17.59 | 28.70 | 40.09 | 64.95 | 35.05 | 100.00 |
| FY2023 Q4 | 22.52 | 6.59 | 15.78 | 30.78 | 42.45 | 64.20 | 35.80 | 100.00 |
| FY2024 Q1 | 23.40 | 5.03 | 17.29 | 29.65 | 41.34 | 64.46 | 35.54 | 100.00 |
| FY2024 Q2 | 20.55 | 6.86 | 17.78 | 29.21 | 41.27 | 64.76 | 35.24 | 100.00 |
| FY2024 Q3 | 25.75 | 5.77 | 15.06 | 27.85 | 39.35 | 63.28 | 36.72 | 100.00 |
| FY2024 Q4 | 25.17 | 6.76 | 13.66 | 28.25 | 40.52 | 62.89 | 37.11 | 100.00 |
| FY2025 Q1 | 23.90 | 5.60 | 15.65 | 27.79 | 40.39 | 64.36 | 35.64 | 100.00 |
| FY2025 Q2 | 19.93 | 7.62 | 16.59 | 27.15 | 39.23 | 64.08 | 35.92 | 100.00 |
| FY2025 Q3 | 27.40 | 6.15 | 14.10 | 26.12 | 37.90 | 64.14 | 35.86 | 100.00 |
| FY2025 Q4 | 27.28 | 6.52 | 13.47 | 26.39 | 37.86 | 63.27 | 36.73 | 100.00 |
| FY2026 Q1 | 26.96 | 5.12 | 15.77 | 27.16 | 56.10 | 67.55 | 32.45 | 100.00 |
| FY2026 Q2 | 19.66 | 7.14 | 17.17 | 29.80 | 54.76 | 64.86 | 35.14 | 100.00 |
| FY2026 Q3 | 26.03 | 5.90 | 14.71 | 28.30 | 51.37 | 63.03 | 36.97 | 100.00 |
| Revenue | GrossProfit | OperatingIncome | NetIncome | OperatingCashFlow | FreeCashFlow | CapEx | Revenue_QoQ_pct | Revenue_YoY_pct | Revenue_TTM | GrossProfit_QoQ_pct | GrossProfit_YoY_pct | GrossProfit_TTM | OperatingIncome_QoQ_pct | OperatingIncome_YoY_pct | OperatingIncome_TTM | NetIncome_QoQ_pct | NetIncome_YoY_pct | NetIncome_TTM | OperatingCashFlow_QoQ_pct | OperatingCashFlow_YoY_pct | OperatingCashFlow_TTM | FreeCashFlow_QoQ_pct | FreeCashFlow_YoY_pct | FreeCashFlow_TTM | CapEx_QoQ_pct | CapEx_YoY_pct | CapEx_TTM | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Quarter | ||||||||||||||||||||||||||||
| FY2016 Q4 | 1,871.00 | 1,014.00 | 67.00 | 41.00 | 155.00 | 62.00 | 93.00 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| FY2017 Q1 | 1,552.00 | 894.60 | -31.20 | -22.00 | 243.00 | 165.00 | 78.00 | -17.05 | NaN | NaN | -11.78 | NaN | NaN | -146.57 | NaN | NaN | -153.66 | NaN | NaN | 56.77 | NaN | NaN | 166.13 | NaN | NaN | -16.13 | NaN | NaN |
| FY2017 Q2 | 1,821.00 | 954.20 | 76.30 | 45.00 | -11.00 | -98.00 | 87.00 | 17.33 | NaN | NaN | 6.66 | NaN | NaN | -344.55 | NaN | NaN | -304.55 | NaN | NaN | -104.53 | NaN | NaN | -159.39 | NaN | NaN | 11.54 | NaN | NaN |
| FY2017 Q3 | 1,714.00 | 983.20 | 128.30 | 82.00 | 618.00 | 558.00 | 60.00 | -5.88 | NaN | 6,958.00 | 3.04 | NaN | 3,846.00 | 68.15 | NaN | 240.40 | 82.22 | NaN | 146.00 | -5,718.18 | NaN | 1,005.00 | -669.39 | NaN | 687.00 | -31.03 | NaN | 318.00 |
| FY2017 Q4 | 1,565.80 | 819.10 | -267.80 | -204.30 | 102.30 | 43.30 | 59.00 | -8.65 | -16.31 | 6,652.80 | -16.69 | -19.22 | 3,651.10 | -308.73 | -499.70 | -94.40 | -349.15 | -598.29 | -99.30 | -83.45 | -34.00 | 952.30 | -92.24 | -30.16 | 668.30 | -1.67 | -36.56 | 284.00 |
| FY2018 Q1 | 1,347.10 | 851.20 | 90.30 | 59.50 | 334.20 | 292.30 | 41.90 | -13.97 | -13.20 | 6,447.90 | 3.92 | -4.85 | 3,607.70 | -133.72 | -389.42 | 27.10 | -129.12 | -370.45 | -17.80 | 226.69 | 37.53 | 1,043.50 | 575.06 | 77.15 | 795.60 | -28.98 | -46.28 | 247.90 |
| FY2018 Q2 | 1,664.20 | 995.80 | 193.30 | 143.80 | 102.80 | 70.00 | 32.80 | 23.54 | -8.61 | 6,291.10 | 16.99 | 4.36 | 3,649.30 | 114.06 | 153.34 | 144.10 | 141.68 | 219.56 | 81.00 | -69.24 | -1,034.55 | 1,157.30 | -76.05 | -171.43 | 963.60 | -21.72 | -62.30 | 193.70 |
| FY2018 Q3 | 1,641.80 | 996.20 | 189.20 | -81.80 | 514.10 | 465.80 | 48.30 | -1.35 | -4.21 | 6,218.90 | 0.04 | 1.32 | 3,662.30 | -2.12 | 47.47 | 205.00 | -156.88 | -199.76 | -82.80 | 400.10 | -16.81 | 1,053.40 | 565.43 | -16.52 | 871.40 | 47.26 | -19.50 | 182.00 |
| FY2018 Q4 | 1,529.20 | 908.50 | 25.40 | 41.30 | 24.00 | -14.60 | 38.60 | -6.86 | -2.34 | 6,182.30 | -8.80 | 10.91 | 3,751.70 | -86.58 | -109.48 | 498.20 | -150.49 | -120.22 | 162.80 | -95.33 | -76.54 | 975.10 | -103.13 | -133.72 | 813.50 | -20.08 | -34.58 | 161.60 |
| FY2019 Q1 | 1,390.60 | 895.70 | 130.10 | 109.00 | 230.60 | 188.30 | 42.30 | -9.06 | 3.23 | 6,225.80 | -1.41 | 5.23 | 3,796.20 | 412.20 | 44.08 | 538.00 | 163.92 | 83.19 | 212.30 | 860.83 | -31.00 | 871.50 | -1,389.73 | -35.58 | 709.50 | 9.59 | 0.95 | 162.00 |
| FY2019 Q2 | 1,690.90 | 1,029.30 | 210.00 | 170.30 | -17.60 | -68.40 | 50.80 | 21.59 | 1.60 | 6,252.50 | 14.92 | 3.36 | 3,829.70 | 61.41 | 8.64 | 554.70 | 56.24 | 18.43 | 238.80 | -107.63 | -117.12 | 751.10 | -136.33 | -197.71 | 571.10 | 20.09 | 54.88 | 180.00 |
| FY2019 Q3 | 1,725.80 | 1,059.50 | 193.80 | 120.00 | 470.10 | 414.00 | 56.10 | 2.06 | 5.12 | 6,336.50 | 2.93 | 6.35 | 3,893.00 | -7.71 | 2.43 | 559.30 | -29.54 | -246.70 | 440.60 | -2,771.02 | -8.56 | 707.10 | -705.26 | -11.12 | 519.30 | 10.43 | 16.15 | 187.80 |
| FY2019 Q4 | 1,505.70 | 901.50 | 27.90 | 31.60 | 100.70 | 52.20 | 48.50 | -12.75 | -1.54 | 6,313.00 | -14.91 | -0.77 | 3,886.00 | -85.60 | 9.84 | 561.80 | -73.67 | -23.49 | 430.90 | -78.58 | 319.58 | 783.80 | -87.39 | -457.53 | 586.10 | -13.55 | 25.65 | 197.70 |
| FY2020 Q1 | 1,428.80 | 920.80 | 143.30 | 117.10 | 197.40 | 148.00 | 49.40 | -5.11 | 2.75 | 6,351.20 | 2.14 | 2.80 | 3,911.10 | 413.62 | 10.15 | 575.00 | 270.57 | 7.43 | 439.00 | 96.03 | -14.40 | 750.60 | 183.52 | -21.40 | 545.80 | 1.86 | 16.78 | 204.80 |
| FY2020 Q2 | 1,706.20 | 1,049.00 | 233.10 | 182.10 | 6.70 | -74.50 | 81.20 | 19.41 | 0.90 | 6,366.50 | 13.92 | 1.91 | 3,930.80 | 62.67 | 11.00 | 598.10 | 55.51 | 6.93 | 450.80 | -96.61 | -138.07 | 774.90 | -150.34 | 8.92 | 539.70 | 64.37 | 59.84 | 235.20 |
| FY2020 Q3 | 1,750.70 | 1,089.10 | 224.40 | 334.10 | 543.90 | 458.50 | 85.40 | 2.61 | 1.44 | 6,391.40 | 3.82 | 2.79 | 3,960.40 | -3.73 | 15.79 | 628.70 | 83.47 | 178.42 | 664.90 | 8,017.91 | 15.70 | 848.70 | -715.44 | 10.75 | 584.20 | 5.17 | 52.23 | 264.50 |
| FY2020 Q4 | 1,274.10 | 594.40 | -283.80 | -249.00 | 6.60 | -47.70 | 54.30 | -27.22 | -15.38 | 6,159.80 | -45.42 | -34.07 | 3,653.30 | -226.47 | -1,117.20 | 317.00 | -174.53 | -887.97 | 384.30 | -98.79 | -93.45 | 754.60 | -110.40 | -191.38 | 484.30 | -36.42 | 11.96 | 270.30 |
| FY2021 Q1 | 487.50 | 348.70 | -168.00 | -127.70 | -70.30 | -91.60 | 21.30 | -61.74 | -65.88 | 5,218.50 | -41.34 | -62.13 | 3,081.20 | -40.80 | -217.24 | 5.70 | -48.71 | -209.05 | 139.50 | -1,165.15 | -135.61 | 486.90 | 92.03 | -161.89 | 244.70 | -60.77 | -56.88 | 242.20 |
| FY2021 Q2 | 1,193.50 | 799.40 | -20.30 | -39.10 | 57.60 | 25.00 | 32.60 | 144.82 | -30.05 | 4,705.80 | 129.25 | -23.79 | 2,831.60 | -87.92 | -108.71 | -247.70 | -69.38 | -121.47 | -81.70 | -181.93 | 759.70 | 537.80 | -127.29 | -133.56 | 344.20 | 53.05 | -59.85 | 193.60 |
| FY2021 Q3 | 1,432.80 | 930.40 | 170.40 | 119.80 | 347.30 | 320.40 | 26.90 | 20.05 | -18.16 | 4,387.90 | 16.39 | -14.57 | 2,672.90 | -939.41 | -24.06 | -301.70 | -406.39 | -64.14 | -296.00 | 502.95 | -36.15 | 341.20 | 1,181.60 | -30.12 | 206.10 | -17.48 | -68.50 | 135.10 |
| FY2021 Q4 | 1,287.00 | 782.90 | -25.70 | -74.10 | 46.30 | 19.30 | 27.00 | -10.18 | 1.01 | 4,400.80 | -15.85 | 31.71 | 2,861.40 | -115.08 | -90.94 | -43.60 | -161.85 | -70.24 | -121.10 | -86.67 | 601.52 | 380.90 | -93.98 | -140.46 | 273.10 | 0.37 | -50.28 | 107.80 |
| FY2022 Q1 | 1,376.30 | 968.10 | 220.60 | 164.70 | 247.60 | 219.40 | 28.20 | 6.94 | 182.32 | 5,289.60 | 23.66 | 177.63 | 3,480.80 | -958.37 | -231.31 | 345.00 | -322.27 | -228.97 | 171.30 | 434.77 | -452.20 | 698.80 | 1,036.79 | -339.52 | 584.10 | 4.44 | 32.39 | 114.70 |
| FY2022 Q2 | 1,504.10 | 1,015.20 | 251.90 | 193.30 | 216.60 | 181.40 | 35.20 | 9.29 | 26.02 | 5,600.20 | 4.87 | 27.00 | 3,696.60 | 14.19 | -1,340.89 | 617.20 | 17.36 | -594.37 | 403.70 | -12.52 | 276.04 | 857.80 | -17.32 | 625.60 | 740.50 | 24.82 | 7.98 | 117.30 |
| FY2022 Q3 | 1,815.40 | 1,198.10 | 289.10 | 217.70 | 357.50 | 307.30 | 50.20 | 20.70 | 26.70 | 5,982.80 | 18.02 | 28.77 | 3,964.30 | 14.77 | 69.66 | 735.90 | 12.62 | 81.72 | 501.60 | 65.05 | 2.94 | 868.00 | 69.40 | -4.09 | 727.40 | 42.61 | 86.62 | 140.60 |
| FY2022 Q4 | 1,522.70 | 966.10 | 36.80 | 24.40 | -105.80 | -159.10 | 53.30 | -16.12 | 18.31 | 6,218.50 | -19.36 | 23.40 | 4,147.50 | -87.27 | -243.19 | 798.40 | -88.79 | -132.93 | 600.10 | -129.59 | -328.51 | 715.90 | -151.77 | -924.35 | 549.00 | 6.18 | 97.41 | 166.90 |
| FY2023 Q1 | 1,490.60 | 1,001.40 | 175.20 | 123.40 | 45.30 | 5.90 | 39.40 | -2.11 | 8.30 | 6,332.80 | 3.65 | 3.44 | 4,180.80 | 376.09 | -20.58 | 753.00 | 405.74 | -25.08 | 558.80 | -142.82 | -81.70 | 513.60 | -103.71 | -97.31 | 335.50 | -26.08 | 39.72 | 178.10 |
| FY2023 Q2 | 1,579.90 | 1,023.10 | 206.70 | 150.50 | -43.40 | -87.90 | 44.50 | 5.99 | 5.04 | 6,408.60 | 2.17 | 0.78 | 4,188.70 | 17.98 | -17.94 | 707.80 | 21.96 | -22.14 | 516.00 | -195.81 | -120.04 | 253.60 | -1,589.83 | -148.46 | 66.20 | 12.94 | 26.42 | 187.40 |
| FY2023 Q3 | 1,832.30 | 1,190.70 | 282.10 | 216.50 | 395.10 | 323.10 | 72.00 | 15.98 | 0.93 | 6,425.50 | 16.38 | -0.62 | 4,181.30 | 36.48 | -2.42 | 700.80 | 43.85 | -0.55 | 514.80 | -1,010.37 | 10.52 | 291.20 | -467.58 | 5.14 | 82.00 | 61.80 | 43.43 | 209.20 |
| FY2023 Q4 | 1,540.80 | 950.60 | 40.20 | 32.30 | 14.00 | -47.60 | 61.60 | -15.91 | 1.19 | 6,443.60 | -20.16 | -1.60 | 4,165.80 | -85.75 | 9.24 | 704.20 | -85.08 | 32.38 | 522.70 | -96.46 | -113.23 | 411.00 | -114.73 | -70.08 | 193.50 | -14.44 | 15.57 | 217.50 |
| FY2024 Q1 | 1,496.50 | 1,032.00 | 166.40 | 132.10 | 270.70 | 231.10 | 39.60 | -2.88 | 0.40 | 6,449.50 | 8.56 | 3.06 | 4,196.40 | 313.93 | -5.02 | 695.40 | 308.98 | 7.05 | 531.40 | 1,833.57 | 497.57 | 636.40 | -585.50 | 3,816.95 | 418.70 | -35.71 | 0.51 | 217.70 |
| FY2024 Q2 | 1,633.00 | 1,070.10 | 164.50 | 146.90 | 72.90 | 30.10 | 42.80 | 9.12 | 3.36 | 6,502.60 | 3.69 | 4.59 | 4,243.40 | -1.14 | -20.42 | 653.20 | 11.20 | -2.39 | 527.80 | -73.07 | -267.97 | 752.70 | -86.98 | -134.24 | 536.70 | 8.08 | -3.82 | 216.00 |
| FY2024 Q3 | 1,934.00 | 1,286.00 | 317.70 | 276.60 | 605.10 | 562.60 | 42.50 | 18.43 | 5.55 | 6,604.30 | 20.18 | 8.00 | 4,338.70 | 93.13 | 12.62 | 688.80 | 88.29 | 27.76 | 587.90 | 730.04 | 53.15 | 962.70 | 1,769.10 | 74.13 | 776.20 | -0.70 | -40.97 | 186.50 |
| FY2024 Q4 | 1,567.90 | 1,043.70 | 107.80 | 90.70 | 121.00 | 81.10 | 39.90 | -18.93 | 1.76 | 6,631.40 | -18.84 | 9.79 | 4,431.80 | -66.07 | 168.16 | 756.40 | -67.21 | 180.80 | 646.30 | -80.00 | 764.29 | 1,069.70 | -85.58 | -270.38 | 904.90 | -6.12 | -35.23 | 164.80 |
| FY2025 Q1 | 1,512.20 | 1,065.80 | 208.50 | 168.60 | 277.30 | 243.90 | 33.40 | -3.55 | 1.05 | 6,647.10 | 2.12 | 3.28 | 4,465.60 | 93.41 | 25.30 | 798.50 | 85.89 | 27.63 | 682.80 | 129.17 | 2.44 | 1,076.30 | 200.74 | 5.54 | 917.70 | -16.29 | -15.66 | 158.60 |
| FY2025 Q2 | 1,726.00 | 1,155.70 | 178.90 | 147.90 | 97.20 | 55.50 | 41.70 | 14.14 | 5.70 | 6,740.10 | 8.43 | 8.00 | 4,551.20 | -14.20 | 8.75 | 812.90 | -12.28 | 0.68 | 683.80 | -64.95 | 33.33 | 1,100.60 | -77.24 | 84.39 | 943.10 | 24.85 | -2.57 | 157.50 |
| FY2025 Q3 | 2,143.50 | 1,466.10 | 389.70 | 297.40 | 738.40 | 677.20 | 61.20 | 24.19 | 10.83 | 6,949.60 | 26.86 | 14.00 | 4,731.30 | 117.83 | 22.66 | 884.90 | 101.08 | 7.52 | 704.60 | 659.67 | 22.03 | 1,233.90 | 1,120.18 | 20.37 | 1,057.70 | 46.76 | 44.00 | 176.20 |
| FY2025 Q4 | 1,697.30 | 1,165.30 | 155.00 | 129.00 | 122.20 | 42.30 | 79.90 | -20.82 | 8.25 | 7,079.00 | -20.52 | 11.65 | 4,852.90 | -60.23 | 43.78 | 932.10 | -56.62 | 42.23 | 742.90 | -83.45 | 0.99 | 1,235.10 | -93.75 | -47.84 | 1,018.90 | 30.56 | 100.25 | 216.20 |
| FY2026 Q1 | 1,719.10 | 1,242.30 | 273.60 | 220.40 | 176.10 | -11.20 | 187.30 | 1.28 | 13.68 | 7,285.90 | 6.61 | 16.56 | 5,029.40 | 76.52 | 31.22 | 997.20 | 70.85 | 30.72 | 794.70 | 44.11 | -36.49 | 1,133.90 | -126.48 | -104.59 | 763.80 | 134.42 | 460.78 | 370.10 |
| FY2026 Q2 | 2,010.70 | 1,366.40 | 245.70 | 207.50 | 53.20 | -40.60 | 93.80 | 16.96 | 16.49 | 7,570.60 | 9.99 | 18.23 | 5,240.10 | -10.20 | 37.34 | 1,064.00 | -5.85 | 40.30 | 854.30 | -69.79 | -45.27 | 1,089.90 | 262.50 | -173.15 | 667.70 | -49.92 | 124.94 | 422.20 |
| FY2026 Q3 | 2,406.00 | 1,681.70 | 503.10 | 361.60 | 779.60 | 704.00 | 75.60 | 19.66 | 12.25 | 7,833.10 | 23.08 | 14.71 | 5,455.70 | 104.76 | 29.10 | 1,177.40 | 74.27 | 21.59 | 918.50 | 1,365.41 | 5.58 | 1,131.10 | -1,833.99 | 3.96 | 694.50 | -19.40 | 23.53 | 436.60 |
4) Ratio Matrix and Heat MapΒΆ
Areas covered included:
- Liquidity
- Profitability
- Efficiency (working-capital velocity)
- Leverage & coverage
- Valuation
We are using the Z-score method as mentioned in class to create the heat map
def _avg(s: pd.Series) -> pd.Series:
return (s + s.shift(1)) / 2
# defining the main fuction to create the ratio matrix and making sure we are covering all families and have no blank datas
def build_ratio_matrix(tidy_df: pd.DataFrame) -> pd.DataFrame:
d = tidy_df.copy()
r = pd.DataFrame(index=d.index)
if {"TotalCurrentAssets", "TotalCurrentLiabilities"}.issubset(d.columns):
r["Liquidity_CurrentRatio"] = d["TotalCurrentAssets"] / d["TotalCurrentLiabilities"].replace(0, np.nan)
if {"Cash", "AccountsReceivable", "TotalCurrentLiabilities"}.issubset(d.columns):
r["Liquidity_QuickRatio"] = (d["Cash"] + d["AccountsReceivable"]) / d["TotalCurrentLiabilities"].replace(0, np.nan)
if {"Cash", "TotalCurrentLiabilities"}.issubset(d.columns):
r["Liquidity_CashRatio"] = d["Cash"] / d["TotalCurrentLiabilities"].replace(0, np.nan)
if {"GrossProfit", "Revenue"}.issubset(d.columns):
r["Profitability_GrossMargin"] = d["GrossProfit"] / d["Revenue"].replace(0, np.nan)
if {"EBIT", "Revenue"}.issubset(d.columns):
r["Profitability_EBITMargin"] = d["EBIT"] / d["Revenue"].replace(0, np.nan)
if {"NetIncome", "Revenue"}.issubset(d.columns):
r["Profitability_NetMargin"] = d["NetIncome"] / d["Revenue"].replace(0, np.nan)
if {"NetIncome_TTM", "AvgAssets"}.issubset(d.columns):
r["Profitability_ROA"] = d["NetIncome_TTM"] / d["AvgAssets"].replace(0, np.nan)
if {"NetIncome_TTM", "AvgEquity"}.issubset(d.columns):
r["Profitability_ROE"] = d["NetIncome_TTM"] / d["AvgEquity"].replace(0, np.nan)
if {"AvgAR", "Revenue_TTM"}.issubset(d.columns):
r["Efficiency_ARDays"] = d["AvgAR"] / (d["Revenue_TTM"] / 365).replace(0, np.nan)
if {"AvgInventory", "COGS_TTM"}.issubset(d.columns):
r["Efficiency_InventoryDays"] = d["AvgInventory"] / (d["COGS_TTM"] / 365).replace(0, np.nan)
if {"AvgAP", "COGS_TTM"}.issubset(d.columns):
r["Efficiency_APDays"] = d["AvgAP"] / (d["COGS_TTM"] / 365).replace(0, np.nan)
if {"Efficiency_ARDays", "Efficiency_InventoryDays", "Efficiency_APDays"}.issubset(r.columns):
r["Efficiency_CCC"] = r["Efficiency_ARDays"] + r["Efficiency_InventoryDays"] - r["Efficiency_APDays"]
if {"Revenue_TTM", "AvgAssets"}.issubset(d.columns):
r["Efficiency_AssetTurnover"] = d["Revenue_TTM"] / d["AvgAssets"].replace(0, np.nan)
if {"TotalDebt", "EBITDA_TTM"}.issubset(d.columns):
r["Leverage_DebtToEBITDA"] = d["TotalDebt"] / d["EBITDA_TTM"].replace(0, np.nan)
if {"TotalDebt", "EquityForBalance"}.issubset(d.columns):
r["Leverage_DebtToEquity"] = d["TotalDebt"] / d["EquityForBalance"].replace(0, np.nan)
if {"TotalDebt", "TotalAssets"}.issubset(d.columns):
r["Leverage_DebtToAssets"] = d["TotalDebt"] / d["TotalAssets"].replace(0, np.nan)
if {"NetDebt", "EBITDA_TTM"}.issubset(d.columns):
r["Leverage_NetDebtToEBITDA"] = d["NetDebt"] / d["EBITDA_TTM"].replace(0, np.nan)
if {"EBIT_TTM", "InterestExpense_TTM"}.issubset(d.columns):
r["Leverage_InterestCoverage"] = d["EBIT_TTM"] / d["InterestExpense_TTM"].replace(0, np.nan)
for col in ["PE", "PS", "PB", "EV_EBITDA", "DebtToMarketCap"]:
if col in d.columns:
r[f"Valuation_{col}"] = d[col]
return r.replace([np.inf, -np.inf], np.nan)
# Using the function in the tidy dataframe
ratio_matrix = build_ratio_matrix(tidy)
display(ratio_matrix.round(3))
# Defining the rations that lower is better.
heat = ratio_matrix.copy()
lower_is_better = [
"Efficiency_ARDays", "Efficiency_InventoryDays", "Efficiency_APDays", "Efficiency_CCC",
"Leverage_DebtToEBITDA", "Leverage_DebtToEquity", "Leverage_DebtToAssets", "Leverage_NetDebtToEBITDA",
"Valuation_PE", "Valuation_PS", "Valuation_PB", "Valuation_EV_EBITDA", "Valuation_DebtToMarketCap",
]
for col in lower_is_better:
if col in heat.columns:
heat[col] = -heat[col]
z = (heat - heat.mean()) / heat.std(ddof=0)
z = z.replace([np.inf, -np.inf], np.nan)
z_plot = z.dropna(axis=1, how="all").T
# Plotting
fig, ax = plt.subplots(figsize=(16, 7))
im = ax.imshow(z_plot.values, aspect="auto")
ax.set_yticks(range(len(z_plot.index)))
ax.set_yticklabels(z_plot.index)
ax.set_xticks(range(len(z_plot.columns)))
ax.set_xticklabels(z_plot.columns, rotation=45, ha="right")
ax.set_title("Quarterly Ratio Dashboard Heat Map (within-ratio z-scores)")
ax.set_xlabel("Fiscal Quarter")
ax.set_ylabel("Ratio")
plt.colorbar(im, ax=ax, fraction=0.046, pad=0.04)
plt.tight_layout()
plt.show()
| Liquidity_CurrentRatio | Liquidity_QuickRatio | Liquidity_CashRatio | Profitability_GrossMargin | Profitability_EBITMargin | Profitability_NetMargin | Profitability_ROA | Profitability_ROE | Efficiency_ARDays | Efficiency_InventoryDays | Efficiency_APDays | Efficiency_CCC | Efficiency_AssetTurnover | Leverage_DebtToEBITDA | Leverage_DebtToEquity | Leverage_DebtToAssets | Leverage_NetDebtToEBITDA | Leverage_InterestCoverage | Valuation_PE | Valuation_PS | Valuation_PB | Valuation_EV_EBITDA | Valuation_DebtToMarketCap | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Quarter | |||||||||||||||||||||||
| FY2016 Q4 | 2.55 | 0.81 | 0.38 | 0.54 | 0.04 | 0.02 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.27 | 0.16 | NaN | NaN | 49.76 | 4.36 | 2.18 | 57.26 | 0.09 |
| FY2017 Q1 | 2.32 | 0.61 | 0.35 | 0.58 | -0.02 | -0.01 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.27 | 0.16 | NaN | NaN | -84.55 | 4.79 | 2.09 | 163.81 | 0.09 |
| FY2017 Q2 | 2.44 | 0.76 | 0.36 | 0.52 | 0.04 | 0.03 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.27 | 0.16 | NaN | NaN | 46.47 | 4.59 | 2.33 | 58.04 | 0.08 |
| FY2017 Q3 | 2.64 | 1.05 | 0.81 | 0.57 | 0.07 | 0.05 | 0.02 | 0.04 | 20.33 | 126.48 | 18.59 | 128.22 | 1.16 | 1.54 | 0.24 | 0.14 | -0.12 | 13.46 | 22.75 | 4.35 | 2.07 | 36.15 | 0.08 |
| FY2017 Q4 | 2.55 | 0.96 | 0.58 | 0.52 | -0.17 | -0.13 | -0.02 | -0.03 | 20.17 | 107.93 | 18.58 | 109.52 | 1.15 | 4.01 | 0.26 | 0.15 | 0.90 | -7.42 | -8.20 | 4.28 | 2.03 | -35.91 | 0.09 |
| FY2018 Q1 | 2.54 | 0.91 | 0.68 | 0.63 | 0.07 | 0.04 | -0.00 | -0.01 | 20.64 | 106.11 | 19.83 | 106.92 | 1.12 | 2.60 | 0.26 | 0.15 | 0.09 | 2.04 | 25.30 | 4.47 | 1.79 | 36.74 | 0.10 |
| FY2018 Q2 | 2.09 | 0.99 | 0.70 | 0.60 | 0.12 | 0.09 | 0.01 | 0.02 | 21.74 | 119.10 | 23.05 | 117.80 | 1.06 | 1.91 | 0.24 | 0.14 | -0.57 | 10.05 | 12.54 | 4.33 | 2.06 | 25.80 | 0.08 |
| FY2018 Q3 | 2.07 | 0.86 | 0.69 | 0.61 | 0.12 | -0.05 | -0.01 | -0.02 | 22.46 | 120.64 | 25.49 | 117.61 | 1.02 | 1.68 | 0.25 | 0.14 | -0.65 | 13.37 | -25.89 | 5.16 | 2.49 | 30.88 | 0.07 |
| FY2018 Q4 | 2.24 | 1.09 | 0.82 | 0.59 | 0.02 | 0.03 | 0.03 | 0.05 | 21.15 | 119.14 | 26.27 | 114.02 | 1.00 | 1.06 | 0.25 | 0.14 | -0.56 | 27.88 | 55.29 | 5.97 | 2.64 | 83.08 | 0.07 |
| FY2019 Q1 | 2.27 | 0.51 | 0.34 | 0.64 | 0.10 | 0.08 | 0.04 | 0.06 | 19.97 | 124.04 | 27.66 | 116.35 | 1.02 | 0.99 | 0.24 | 0.14 | 0.36 | 31.41 | 23.62 | 7.40 | 3.01 | 51.06 | 0.06 |
| FY2019 Q2 | 2.98 | 0.79 | 0.45 | 0.61 | 0.13 | 0.10 | 0.04 | 0.07 | 20.21 | 141.96 | 30.49 | 131.68 | 1.03 | 1.07 | 0.27 | 0.15 | 0.43 | 30.42 | 16.42 | 6.61 | 3.23 | 39.79 | 0.06 |
| FY2019 Q3 | 2.90 | 0.77 | 0.53 | 0.61 | 0.12 | 0.07 | 0.07 | 0.13 | 21.21 | 142.59 | 27.72 | 136.07 | 1.03 | 1.05 | 0.28 | 0.15 | 0.28 | 30.49 | 17.01 | 4.73 | 2.43 | 30.39 | 0.08 |
| FY2019 Q4 | 3.00 | 0.82 | 0.49 | 0.60 | 0.03 | 0.02 | 0.07 | 0.13 | 20.30 | 130.26 | 27.93 | 122.63 | 1.05 | 1.04 | 0.28 | 0.15 | 0.38 | 29.14 | 81.05 | 6.80 | 3.12 | 96.49 | 0.07 |
| FY2020 Q1 | 2.30 | 0.59 | 0.41 | 0.64 | 0.11 | 0.08 | 0.07 | 0.14 | 19.79 | 135.10 | 41.42 | 113.48 | 0.96 | 3.00 | 0.89 | 0.36 | 2.27 | 30.08 | 18.96 | 6.22 | 2.95 | 50.30 | 0.08 |
| FY2020 Q2 | 1.95 | 0.59 | 0.32 | 0.61 | 0.14 | 0.11 | 0.06 | 0.15 | 22.18 | 149.94 | 52.58 | 119.54 | 0.87 | 3.10 | 0.97 | 0.39 | 2.50 | 33.70 | 9.92 | 4.24 | 2.48 | 30.90 | 0.10 |
| FY2020 Q3 | 1.94 | 0.81 | 0.61 | 0.62 | 0.13 | 0.19 | 0.09 | 0.22 | 23.77 | 143.92 | 46.39 | 121.31 | 0.87 | 3.01 | 0.91 | 0.38 | 1.86 | 37.25 | 6.56 | 5.01 | 2.81 | 34.71 | 0.08 |
| FY2020 Q4 | 1.61 | 0.91 | 0.78 | 0.47 | -0.22 | -0.20 | 0.05 | 0.13 | 18.56 | 119.47 | 37.45 | 100.58 | 0.84 | 5.26 | 1.20 | 0.44 | 2.62 | 19.55 | -5.03 | 3.93 | 1.86 | -30.94 | 0.23 |
| FY2021 Q1 | 2.59 | 1.72 | 1.65 | 0.71 | -0.33 | -0.26 | 0.02 | 0.05 | 13.49 | 128.88 | 33.39 | 108.99 | 0.69 | 13.33 | 1.55 | 0.51 | 5.08 | 1.31 | -9.67 | 10.13 | 1.93 | -64.96 | 0.39 |
| FY2021 Q2 | 2.52 | 1.52 | 1.29 | 0.67 | -0.01 | -0.03 | -0.01 | -0.03 | 17.94 | 161.66 | 41.74 | 137.86 | 0.61 | 103.67 | 1.42 | 0.47 | 45.86 | -7.23 | -33.40 | 4.38 | 2.05 | 150.88 | 0.31 |
| FY2021 Q3 | 2.55 | 1.79 | 1.56 | 0.65 | 0.12 | 0.08 | -0.04 | -0.11 | 30.26 | 186.54 | 65.92 | 150.88 | 0.55 | -111.33 | 1.38 | 0.45 | -32.61 | -7.29 | 15.50 | 5.18 | 2.76 | 36.27 | 0.22 |
| FY2021 Q4 | 2.65 | 1.91 | 1.63 | 0.61 | -0.02 | -0.06 | -0.01 | -0.05 | 34.22 | 192.65 | 81.91 | 144.96 | 0.55 | 16.36 | 1.39 | 0.46 | 4.71 | -0.54 | -30.25 | 6.97 | 3.44 | 245.91 | 0.18 |
| FY2022 Q1 | 2.07 | 1.40 | 1.23 | 0.70 | 0.16 | 0.12 | 0.02 | 0.06 | 28.25 | 157.60 | 73.27 | 112.58 | 0.67 | 5.88 | 1.30 | 0.44 | 1.56 | 6.90 | 13.57 | 6.50 | 3.29 | 35.22 | 0.18 |
| FY2022 Q2 | 2.05 | 1.24 | 1.06 | 0.68 | 0.17 | 0.13 | 0.05 | 0.14 | 25.63 | 165.97 | 78.78 | 112.83 | 0.69 | 4.04 | 1.22 | 0.43 | 1.27 | 11.85 | 11.15 | 5.73 | 3.01 | 31.59 | 0.19 |
| FY2022 Q3 | 1.88 | 1.11 | 0.94 | 0.66 | 0.16 | 0.12 | 0.06 | 0.18 | 25.32 | 167.93 | 83.56 | 109.68 | 0.73 | 3.52 | 1.26 | 0.42 | 1.19 | 13.73 | 9.43 | 4.52 | 3.02 | 27.02 | 0.20 |
| FY2022 Q4 | 1.87 | 1.01 | 0.83 | 0.63 | 0.03 | 0.02 | 0.08 | 0.23 | 23.95 | 168.00 | 81.20 | 110.74 | 0.78 | 3.27 | 1.34 | 0.44 | 1.47 | 14.97 | 81.36 | 5.21 | 3.13 | 91.58 | 0.21 |
| FY2023 Q1 | 2.05 | 1.03 | 0.83 | 0.67 | 0.12 | 0.08 | 0.08 | 0.23 | 21.78 | 182.80 | 85.72 | 118.86 | 0.86 | 2.85 | 1.19 | 0.40 | 1.37 | 14.46 | 12.88 | 4.26 | 2.69 | 33.71 | 0.18 |
| FY2023 Q2 | 2.01 | 0.93 | 0.65 | 0.65 | 0.13 | 0.10 | 0.07 | 0.22 | 23.92 | 200.56 | 87.15 | 137.33 | 0.94 | 2.93 | 1.22 | 0.41 | 1.76 | 14.82 | 9.59 | 3.65 | 2.56 | 28.26 | 0.20 |
| FY2023 Q3 | 2.08 | 1.14 | 0.90 | 0.65 | 0.16 | 0.12 | 0.07 | 0.22 | 25.95 | 203.29 | 78.56 | 150.68 | 0.93 | 2.99 | 1.14 | 0.40 | 1.33 | 15.30 | 8.11 | 3.83 | 2.85 | 23.83 | 0.16 |
| FY2023 Q4 | 2.23 | 1.32 | 1.02 | 0.62 | 0.04 | 0.02 | 0.08 | 0.21 | 24.69 | 185.06 | 67.27 | 142.47 | 0.93 | 3.02 | 1.19 | 0.42 | 1.42 | 18.13 | 59.96 | 5.03 | 3.19 | 80.17 | 0.15 |
| FY2024 Q1 | 2.18 | 1.23 | 1.01 | 0.69 | 0.12 | 0.09 | 0.08 | 0.22 | 22.45 | 182.99 | 66.42 | 139.02 | 0.94 | 2.95 | 1.16 | 0.41 | 1.28 | 19.14 | 15.38 | 5.43 | 3.33 | 39.17 | 0.14 |
| FY2024 Q2 | 2.11 | 1.14 | 0.86 | 0.66 | 0.11 | 0.09 | 0.08 | 0.22 | 22.65 | 192.51 | 73.39 | 141.77 | 0.96 | 2.97 | 1.17 | 0.41 | 1.49 | 18.03 | 12.96 | 4.66 | 3.21 | 38.50 | 0.15 |
| FY2024 Q3 | 2.17 | 1.31 | 1.07 | 0.67 | 0.18 | 0.14 | 0.09 | 0.24 | 23.90 | 181.28 | 70.23 | 134.94 | 0.96 | 2.80 | 1.07 | 0.39 | 0.97 | 19.97 | 8.47 | 4.85 | 3.64 | 26.04 | 0.12 |
| FY2024 Q4 | 2.29 | 1.44 | 1.13 | 0.67 | 0.08 | 0.06 | 0.10 | 0.26 | 23.40 | 162.40 | 61.73 | 124.07 | 0.97 | 2.55 | 1.09 | 0.41 | 0.97 | 19.42 | 33.28 | 7.70 | 4.93 | 73.07 | 0.09 |
| FY2025 Q1 | 2.19 | 1.25 | 1.01 | 0.70 | 0.15 | 0.11 | 0.10 | 0.28 | 22.47 | 162.41 | 67.76 | 117.11 | 1.00 | 2.46 | 1.13 | 0.40 | 1.00 | 20.10 | 16.41 | 7.32 | 4.67 | 43.13 | 0.10 |
| FY2025 Q2 | 1.72 | 0.90 | 0.65 | 0.67 | 0.12 | 0.09 | 0.10 | 0.28 | 24.09 | 180.67 | 81.17 | 123.60 | 1.00 | 2.40 | 1.09 | 0.39 | 1.18 | 20.01 | 20.80 | 7.13 | 5.04 | 53.31 | 0.09 |
| FY2025 Q3 | 1.75 | 1.07 | 0.87 | 0.68 | 0.18 | 0.14 | 0.10 | 0.28 | 25.03 | 174.95 | 81.05 | 118.92 | 1.00 | 2.30 | 1.06 | 0.38 | 0.64 | 20.78 | 12.06 | 6.69 | 5.65 | 33.56 | 0.08 |
| FY2025 Q4 | 1.78 | 1.12 | 0.90 | 0.69 | 0.10 | 0.08 | 0.10 | 0.29 | 23.07 | 159.72 | 75.87 | 106.91 | 1.00 | 2.20 | 1.03 | 0.38 | 0.61 | 22.56 | 26.00 | 7.90 | 5.18 | 62.25 | 0.09 |
| FY2026 Q1 | 1.84 | 1.09 | 0.91 | 0.72 | 0.17 | 0.13 | 0.11 | 0.31 | 21.44 | 175.65 | 84.53 | 112.57 | 0.98 | 3.40 | 1.73 | 0.56 | 1.77 | 23.64 | 19.12 | 9.81 | 6.70 | 55.41 | 0.16 |
| FY2026 Q2 | 2.04 | 1.07 | 0.79 | 0.68 | 0.12 | 0.10 | 0.11 | 0.34 | 22.21 | 194.48 | 89.41 | 127.28 | 1.00 | 3.04 | 1.56 | 0.55 | 1.95 | 22.39 | 23.49 | 9.70 | 7.55 | 73.32 | 0.14 |
| FY2026 Q3 | 2.10 | 1.25 | 1.02 | 0.70 | 0.20 | 0.15 | 0.12 | 0.34 | 22.96 | 185.06 | 82.63 | 125.38 | 1.03 | 2.85 | 1.39 | 0.51 | 1.40 | 23.25 | 15.13 | 9.09 | 7.58 | 44.45 | 0.12 |
5) Red-Flag RulesΒΆ
Rules very simple using a buffer on the median to set "maximum bands" if value is above or below it it trigger the flag.
# Defining the buffers
def build_activation_red_flags(
ratios: pd.DataFrame,
rolling_window: int | None = None,
min_periods: int = 3,
liquidity_buffer: float = 0.1,
leverage_buffer: float = 0.15,
coverage_buffer: float = 0.15,
operating_buffer: float = 0.1,
ccc_jump_threshold: float = 20.0,
wc_buffer: float = 0.2,
) -> pd.DataFrame:
r = ratios.copy()
flags = pd.DataFrame(index=r.index)
if rolling_window is None:
rolling_window = 12 if len(r) >= 12 else max(3, len(r) // 2)
mp = min(min_periods, rolling_window)
def _get_col(candidates: list[str]) -> pd.Series | None:
for c in candidates:
if c in r.columns:
return pd.to_numeric(r[c], errors="coerce")
return None
def _rm(s: pd.Series) -> pd.Series:
return s.rolling(rolling_window, min_periods=mp).mean()
def _or_reduce(conds: list[pd.Series]) -> pd.Series:
if not conds:
return pd.Series(False, index=r.index)
arr = [c.fillna(False).astype(bool) for c in conds]
return pd.Series(np.logical_or.reduce(arr), index=r.index)
liquidity_conditions = []
quick = _get_col(["QuickRatio", "Liquidity_QuickRatio"])
if quick is not None:
quick_rm = _rm(quick)
liquidity_conditions.append(quick < quick_rm * (1 - liquidity_buffer))
current = _get_col(["CurrentRatio", "Liquidity_CurrentRatio"])
if current is not None:
current_rm = _rm(current)
liquidity_conditions.append(current < current_rm * (1 - liquidity_buffer))
cash = _get_col(["CashRatio", "Liquidity_CashRatio"])
if cash is not None:
cash_rm = _rm(cash)
liquidity_conditions.append(cash < cash_rm * (1 - liquidity_buffer))
flags["RF1_LiquidityStress"] = _or_reduce(liquidity_conditions)
# RF2: Leverage Risk
leverage_conditions = []
debt_ebitda = _get_col(["DebtEBITDA", "Leverage_DebtToEBITDA"])
if debt_ebitda is not None:
debt_ebitda_rm = _rm(debt_ebitda)
leverage_conditions.append(debt_ebitda > debt_ebitda_rm * (1 + leverage_buffer))
net_debt_ebitda = _get_col(["NetDebtEBITDA", "Leverage_NetDebtToEBITDA"])
if net_debt_ebitda is not None:
nde_rm = _rm(net_debt_ebitda)
leverage_conditions.append(net_debt_ebitda > nde_rm * (1 + leverage_buffer))
debt_to_equity = _get_col(["DebtToEquity", "Leverage_DebtToEquity"])
if debt_to_equity is not None:
dte_rm = _rm(debt_to_equity)
leverage_conditions.append(debt_to_equity > dte_rm * (1 + leverage_buffer))
debt_to_assets = _get_col(["DebtToAssets", "Leverage_DebtToAssets"])
if debt_to_assets is not None:
dta_rm = _rm(debt_to_assets)
leverage_conditions.append(debt_to_assets > dta_rm * (1 + leverage_buffer))
int_cov = _get_col(["IntCoverage", "Leverage_InterestCoverage"])
if int_cov is not None:
int_cov_rm = _rm(int_cov)
leverage_conditions.append(int_cov < int_cov_rm * (1 - coverage_buffer))
flags["RF2_LeverageRisk"] = _or_reduce(leverage_conditions)
# RF3: Operating Deterioration
op_conditions = []
for candidates in [["GrossMargin", "Profitability_GrossMargin"],
["EBITMargin", "Profitability_EBITMargin"],
["ROA", "Profitability_ROA"]]:
s = _get_col(candidates)
if s is None:
continue
s_rm = _rm(s)
op_conditions.append((s < s_rm * (1 - operating_buffer)) & (s.diff() < 0))
flags["RF3_OperatingDeterioration"] = _or_reduce(op_conditions)
# RF4: Working Capital Shock
wc_conditions = []
ccc = _get_col(["CCC", "Efficiency_CCC"])
if ccc is not None:
ccc_rm = _rm(ccc)
wc_conditions.append((ccc.diff() > ccc_jump_threshold) | (ccc > ccc_rm * (1 + wc_buffer)))
inv_days = _get_col(["InventoryDays", "Efficiency_InventoryDays"])
if inv_days is not None:
inv_rm = _rm(inv_days)
wc_conditions.append((inv_days > inv_rm * (1 + wc_buffer)) & (inv_days.diff() > 0))
ar_days = _get_col(["ARDays", "Efficiency_ARDays"])
if ar_days is not None:
ar_rm = _rm(ar_days)
wc_conditions.append((ar_days > ar_rm * (1 + wc_buffer)) & (ar_days.diff() > 0))
flags["RF4_WorkingCapitalShock"] = _or_reduce(wc_conditions)
return flags
flags = build_activation_red_flags(ratio_matrix)
# Making the Redflag text and identification
driver_text = {
"RF1_LiquidityStress": "Liquidity ratios are below buffered rolling norms, indicating tighter near-term funding flexibility.",
"RF2_LeverageRisk": "Leverage metrics are above buffered rolling norms and/or coverage is below buffered norms.",
"RF3_OperatingDeterioration": "Core operating metrics are below buffered rolling trend and still moving down.",
"RF4_WorkingCapitalShock": "CCC and/or working-capital days are rising above buffered rolling baseline (cash conversion drag).",
}
# Activating the flags
activated_flags = (
flags[flags.any(axis=1)]
.stack()
.reset_index()
.rename(columns={"level_0": "Year", "level_1": "Rule", 0: "Triggered"})
)
activated_flags = activated_flags[activated_flags["Triggered"]].drop(columns="Triggered")
if len(activated_flags) > 0:
activated_flags["Driver"] = activated_flags["Rule"].map(driver_text)
# printing the outputs
print("Flag matrix (rolling-mean + buffer rules):")
display(flags)
print()
print("Activated flags with drivers:")
if len(activated_flags) == 0:
print("No flags triggered under current thresholds.")
else:
display(activated_flags)
# Created a flag count to help us in the executive summary
flag_counts = flags.sum(axis=1).rename("RedFlagCount")
display(flag_counts.to_frame())
x = flag_counts.sum()
print(f"Total Red flags activated: {x}")
Flag matrix (rolling-mean + buffer rules):
| RF1_LiquidityStress | RF2_LeverageRisk | RF3_OperatingDeterioration | RF4_WorkingCapitalShock | |
|---|---|---|---|---|
| Quarter | ||||
| FY2016 Q4 | False | False | False | False |
| FY2017 Q1 | False | False | False | False |
| FY2017 Q2 | False | False | False | False |
| FY2017 Q3 | False | False | False | False |
| FY2017 Q4 | False | False | True | False |
| FY2018 Q1 | False | True | False | False |
| FY2018 Q2 | True | False | False | False |
| FY2018 Q3 | True | False | True | False |
| FY2018 Q4 | False | False | True | False |
| FY2019 Q1 | True | True | False | False |
| FY2019 Q2 | True | True | False | False |
| FY2019 Q3 | False | True | False | False |
| FY2019 Q4 | True | True | True | False |
| FY2020 Q1 | True | True | False | False |
| FY2020 Q2 | True | True | False | False |
| FY2020 Q3 | True | True | False | False |
| FY2020 Q4 | True | True | True | False |
| FY2021 Q1 | False | True | True | False |
| FY2021 Q2 | False | True | True | True |
| FY2021 Q3 | False | True | True | True |
| FY2021 Q4 | False | True | True | True |
| FY2022 Q1 | True | True | False | False |
| FY2022 Q2 | True | True | False | False |
| FY2022 Q3 | True | False | False | False |
| FY2022 Q4 | True | False | True | False |
| FY2023 Q1 | True | False | False | False |
| FY2023 Q2 | True | False | False | True |
| FY2023 Q3 | True | False | False | True |
| FY2023 Q4 | False | False | True | False |
| FY2024 Q1 | False | False | False | False |
| FY2024 Q2 | True | True | False | False |
| FY2024 Q3 | False | False | False | False |
| FY2024 Q4 | False | False | True | False |
| FY2025 Q1 | False | False | False | False |
| FY2025 Q2 | True | False | False | False |
| FY2025 Q3 | True | False | False | False |
| FY2025 Q4 | True | False | True | False |
| FY2026 Q1 | False | True | False | False |
| FY2026 Q2 | True | True | False | False |
| FY2026 Q3 | False | True | False | False |
Activated flags with drivers:
| Quarter | Rule | Driver | |
|---|---|---|---|
| 2 | FY2017 Q4 | RF3_OperatingDeterioration | Core operating metrics are below buffered roll... |
| 5 | FY2018 Q1 | RF2_LeverageRisk | Leverage metrics are above buffered rolling no... |
| 8 | FY2018 Q2 | RF1_LiquidityStress | Liquidity ratios are below buffered rolling no... |
| 12 | FY2018 Q3 | RF1_LiquidityStress | Liquidity ratios are below buffered rolling no... |
| 14 | FY2018 Q3 | RF3_OperatingDeterioration | Core operating metrics are below buffered roll... |
| 18 | FY2018 Q4 | RF3_OperatingDeterioration | Core operating metrics are below buffered roll... |
| 20 | FY2019 Q1 | RF1_LiquidityStress | Liquidity ratios are below buffered rolling no... |
| 21 | FY2019 Q1 | RF2_LeverageRisk | Leverage metrics are above buffered rolling no... |
| 24 | FY2019 Q2 | RF1_LiquidityStress | Liquidity ratios are below buffered rolling no... |
| 25 | FY2019 Q2 | RF2_LeverageRisk | Leverage metrics are above buffered rolling no... |
| 29 | FY2019 Q3 | RF2_LeverageRisk | Leverage metrics are above buffered rolling no... |
| 32 | FY2019 Q4 | RF1_LiquidityStress | Liquidity ratios are below buffered rolling no... |
| 33 | FY2019 Q4 | RF2_LeverageRisk | Leverage metrics are above buffered rolling no... |
| 34 | FY2019 Q4 | RF3_OperatingDeterioration | Core operating metrics are below buffered roll... |
| 36 | FY2020 Q1 | RF1_LiquidityStress | Liquidity ratios are below buffered rolling no... |
| 37 | FY2020 Q1 | RF2_LeverageRisk | Leverage metrics are above buffered rolling no... |
| 40 | FY2020 Q2 | RF1_LiquidityStress | Liquidity ratios are below buffered rolling no... |
| 41 | FY2020 Q2 | RF2_LeverageRisk | Leverage metrics are above buffered rolling no... |
| 44 | FY2020 Q3 | RF1_LiquidityStress | Liquidity ratios are below buffered rolling no... |
| 45 | FY2020 Q3 | RF2_LeverageRisk | Leverage metrics are above buffered rolling no... |
| 48 | FY2020 Q4 | RF1_LiquidityStress | Liquidity ratios are below buffered rolling no... |
| 49 | FY2020 Q4 | RF2_LeverageRisk | Leverage metrics are above buffered rolling no... |
| 50 | FY2020 Q4 | RF3_OperatingDeterioration | Core operating metrics are below buffered roll... |
| 53 | FY2021 Q1 | RF2_LeverageRisk | Leverage metrics are above buffered rolling no... |
| 54 | FY2021 Q1 | RF3_OperatingDeterioration | Core operating metrics are below buffered roll... |
| 57 | FY2021 Q2 | RF2_LeverageRisk | Leverage metrics are above buffered rolling no... |
| 58 | FY2021 Q2 | RF3_OperatingDeterioration | Core operating metrics are below buffered roll... |
| 59 | FY2021 Q2 | RF4_WorkingCapitalShock | CCC and/or working-capital days are rising abo... |
| 61 | FY2021 Q3 | RF2_LeverageRisk | Leverage metrics are above buffered rolling no... |
| 62 | FY2021 Q3 | RF3_OperatingDeterioration | Core operating metrics are below buffered roll... |
| 63 | FY2021 Q3 | RF4_WorkingCapitalShock | CCC and/or working-capital days are rising abo... |
| 65 | FY2021 Q4 | RF2_LeverageRisk | Leverage metrics are above buffered rolling no... |
| 66 | FY2021 Q4 | RF3_OperatingDeterioration | Core operating metrics are below buffered roll... |
| 67 | FY2021 Q4 | RF4_WorkingCapitalShock | CCC and/or working-capital days are rising abo... |
| 68 | FY2022 Q1 | RF1_LiquidityStress | Liquidity ratios are below buffered rolling no... |
| 69 | FY2022 Q1 | RF2_LeverageRisk | Leverage metrics are above buffered rolling no... |
| 72 | FY2022 Q2 | RF1_LiquidityStress | Liquidity ratios are below buffered rolling no... |
| 73 | FY2022 Q2 | RF2_LeverageRisk | Leverage metrics are above buffered rolling no... |
| 76 | FY2022 Q3 | RF1_LiquidityStress | Liquidity ratios are below buffered rolling no... |
| 80 | FY2022 Q4 | RF1_LiquidityStress | Liquidity ratios are below buffered rolling no... |
| 82 | FY2022 Q4 | RF3_OperatingDeterioration | Core operating metrics are below buffered roll... |
| 84 | FY2023 Q1 | RF1_LiquidityStress | Liquidity ratios are below buffered rolling no... |
| 88 | FY2023 Q2 | RF1_LiquidityStress | Liquidity ratios are below buffered rolling no... |
| 91 | FY2023 Q2 | RF4_WorkingCapitalShock | CCC and/or working-capital days are rising abo... |
| 92 | FY2023 Q3 | RF1_LiquidityStress | Liquidity ratios are below buffered rolling no... |
| 95 | FY2023 Q3 | RF4_WorkingCapitalShock | CCC and/or working-capital days are rising abo... |
| 98 | FY2023 Q4 | RF3_OperatingDeterioration | Core operating metrics are below buffered roll... |
| 100 | FY2024 Q2 | RF1_LiquidityStress | Liquidity ratios are below buffered rolling no... |
| 101 | FY2024 Q2 | RF2_LeverageRisk | Leverage metrics are above buffered rolling no... |
| 106 | FY2024 Q4 | RF3_OperatingDeterioration | Core operating metrics are below buffered roll... |
| 108 | FY2025 Q2 | RF1_LiquidityStress | Liquidity ratios are below buffered rolling no... |
| 112 | FY2025 Q3 | RF1_LiquidityStress | Liquidity ratios are below buffered rolling no... |
| 116 | FY2025 Q4 | RF1_LiquidityStress | Liquidity ratios are below buffered rolling no... |
| 118 | FY2025 Q4 | RF3_OperatingDeterioration | Core operating metrics are below buffered roll... |
| 121 | FY2026 Q1 | RF2_LeverageRisk | Leverage metrics are above buffered rolling no... |
| 124 | FY2026 Q2 | RF1_LiquidityStress | Liquidity ratios are below buffered rolling no... |
| 125 | FY2026 Q2 | RF2_LeverageRisk | Leverage metrics are above buffered rolling no... |
| 129 | FY2026 Q3 | RF2_LeverageRisk | Leverage metrics are above buffered rolling no... |
| RedFlagCount | |
|---|---|
| Quarter | |
| FY2016 Q4 | 0 |
| FY2017 Q1 | 0 |
| FY2017 Q2 | 0 |
| FY2017 Q3 | 0 |
| FY2017 Q4 | 1 |
| FY2018 Q1 | 1 |
| FY2018 Q2 | 1 |
| FY2018 Q3 | 2 |
| FY2018 Q4 | 1 |
| FY2019 Q1 | 2 |
| FY2019 Q2 | 2 |
| FY2019 Q3 | 1 |
| FY2019 Q4 | 3 |
| FY2020 Q1 | 2 |
| FY2020 Q2 | 2 |
| FY2020 Q3 | 2 |
| FY2020 Q4 | 3 |
| FY2021 Q1 | 2 |
| FY2021 Q2 | 3 |
| FY2021 Q3 | 3 |
| FY2021 Q4 | 3 |
| FY2022 Q1 | 2 |
| FY2022 Q2 | 2 |
| FY2022 Q3 | 1 |
| FY2022 Q4 | 2 |
| FY2023 Q1 | 1 |
| FY2023 Q2 | 2 |
| FY2023 Q3 | 2 |
| FY2023 Q4 | 1 |
| FY2024 Q1 | 0 |
| FY2024 Q2 | 2 |
| FY2024 Q3 | 0 |
| FY2024 Q4 | 1 |
| FY2025 Q1 | 0 |
| FY2025 Q2 | 1 |
| FY2025 Q3 | 1 |
| FY2025 Q4 | 2 |
| FY2026 Q1 | 1 |
| FY2026 Q2 | 2 |
| FY2026 Q3 | 1 |
Total Red flags activated: 58
Going above and beyondΒΆ
Nice visuals to wrap up our analysis
# Defining the function to plor possitive only for better analysis
def mountain_plot_positive_only(cs_df: pd.DataFrame, cols: list[str], title: str, y_label: str, percent: bool = True):
plot_df = cs_df[cols].copy().apply(pd.to_numeric, errors="coerce")
keep, dropped = [], []
for c in plot_df.columns:
s = plot_df[c].dropna()
if s.empty:
dropped.append(c)
continue
all_nonneg = (s >= 0).all()
all_nonpos = (s <= 0).all()
if all_nonneg or all_nonpos:
keep.append(c)
else:
dropped.append(c)
plot_df = plot_df[keep]
for c in plot_df.columns:
if (plot_df[c].dropna() <= 0).all():
plot_df[c] = -plot_df[c]
if percent:
plot_df = plot_df * 100
if plot_df.shape[1] == 0:
print(f"Skipped '{title}': no single-sign columns available for stacking.")
return
# Plotting
ax = plot_df.plot(kind="area", stacked=True, figsize=(13, 5), alpha=0.85)
plt.title(title)
plt.ylabel(y_label)
plt.xlabel("Fiscal Quarter")
plt.xticks(rotation=45, ha="right")
plt.legend(loc="upper left")
plt.tight_layout()
plt.show()
if dropped:
print("Dropped (mixed-sign/empty columns):", dropped)
# Preping the first mountain graph to plot
is_cs_plot = pd.DataFrame(index=tidy.index)
for col in ["COGS", "OperatingExpenses"]:
if col in tidy.columns:
is_cs_plot[col] = tidy[col] / tidy["Revenue"].replace(0, np.nan)
if not is_cs_plot.empty:
mountain_plot_positive_only(
cs_df=is_cs_plot,
cols=list(is_cs_plot.columns),
title="Quarterly Income Statement Expense Intensity (Mountain)",
y_label="% of Revenue",
percent=True,
)
# Preping the margin plot
margin_overlay = pd.DataFrame(index=tidy.index)
if {"GrossProfit", "Revenue"}.issubset(tidy.columns):
margin_overlay["Gross Margin"] = tidy["GrossProfit"] / tidy["Revenue"].replace(0, np.nan)
if {"OperatingIncome", "Revenue"}.issubset(tidy.columns):
margin_overlay["Operating Margin"] = tidy["OperatingIncome"] / tidy["Revenue"].replace(0, np.nan)
if {"NetIncome", "Revenue"}.issubset(tidy.columns):
margin_overlay["Net Margin"] = tidy["NetIncome"] / tidy["Revenue"].replace(0, np.nan)
if not is_cs_plot.empty:
ax = (is_cs_plot.abs() * 100).plot(kind="area", stacked=True, figsize=(13, 5), alpha=0.75)
if not margin_overlay.empty:
(margin_overlay * 100).plot(ax=ax, linewidth=2)
plt.title("Quarterly Expense Mountain + Margin Trend Lines")
plt.ylabel("% of Revenue")
plt.xlabel("Fiscal Quarter")
plt.xticks(rotation=45, ha="right")
plt.legend(loc="upper left")
plt.tight_layout()
plt.show()
# Preping the bs mountain graph to plot
bs_cs_plot = pd.DataFrame(index=tidy.index)
for col in ["Cash", "AccountsReceivable", "Inventory", "PPENet"]:
if col in tidy.columns:
bs_cs_plot[col] = tidy[col] / tidy["TotalAssets"].replace(0, np.nan)
if not bs_cs_plot.empty:
mountain_plot_positive_only(
cs_df=bs_cs_plot,
cols=list(bs_cs_plot.columns),
title="Quarterly Balance Sheet Asset Mix (Mountain)",
y_label="% of Total Assets",
percent=True,
)
# Preping the cf mountain graph to plot
cf_cs_plot = pd.DataFrame(index=tidy.index)
if "OperatingCashFlow" in tidy.columns:
cf_cs_plot["OperatingCashFlow"] = tidy["OperatingCashFlow"] / tidy["Revenue"].replace(0, np.nan)
if "CapEx" in tidy.columns:
cf_cs_plot["CapEx_Outflow"] = -tidy["CapEx"] / tidy["Revenue"].replace(0, np.nan)
if "FreeCashFlow" in tidy.columns:
cf_cs_plot["FreeCashFlow"] = tidy["FreeCashFlow"] / tidy["Revenue"].replace(0, np.nan)
if not cf_cs_plot.empty:
mountain_plot_positive_only(
cs_df=cf_cs_plot,
cols=list(cf_cs_plot.columns),
title="Quarterly Cash-Flow Components (Mountain)",
y_label="% of Revenue",
percent=True,
)
Dropped (mixed-sign/empty columns): ['OperatingCashFlow', 'FreeCashFlow']
Export for control purposeΒΆ
Not required in the deliverable. However we can keep track of the work and use as inputs for future projects
# Export key deliverables
(out_dir / "unit1_common_size_income_pct.csv").write_text(common_is.round(4).to_csv())
(out_dir / "unit1_common_size_balance_pct.csv").write_text(common_bs.round(4).to_csv())
(out_dir / "unit1_trend_diagnostics.csv").write_text(trend.round(4).to_csv())
(out_dir / "unit1_ratio_matrix.csv").write_text(ratio_matrix.round(6).to_csv())
(out_dir / "unit1_red_flags_matrix.csv").write_text(flags.to_csv())
(out_dir / "unit1_red_flags_activated.csv").write_text(activated_flags.to_csv(index=False))
quarterly_pack_path = QUARTERLY_EXPORT_DIR / "RL_quarterly_historical_pack_usdm.csv"
quarterly_audit_path = QUARTERLY_EXPORT_DIR / "RL_quarterly_historical_audit.csv"
tidy.to_csv(quarterly_pack_path)
audit.to_csv(quarterly_audit_path)
print("Export complete. Files saved in:", out_dir)
print("Quarterly historical pack:", quarterly_pack_path)
print("Quarterly audit table :", quarterly_audit_path)
Export complete. Files saved in: /Users/theomachado/Library/CloudStorage/OneDrive-UniversityofFlorida/Spring 2026/FIN4453 - OFC/Mini-projects/outputs Quarterly historical pack: /Users/theomachado/Library/CloudStorage/OneDrive-UniversityofFlorida/Spring 2026/FIN4453 - OFC/DATA/csv_export/RL_quarterly_historical_pack_usdm.csv Quarterly audit table : /Users/theomachado/Library/CloudStorage/OneDrive-UniversityofFlorida/Spring 2026/FIN4453 - OFC/DATA/csv_export/RL_quarterly_historical_audit.csv