Module 4 Mini-Project: Constructing the Cash Flow Statement¶

Company: Ralph lauren ticker: RL Analyst: Theo and Olivia

  • Dr. Mascio please note: We left a output section for control the generated data and use in the loading for final project in section 9 and as highlighted the need to go "Above and beyond" by you and luke I added a graph to better visualize the results and delta cash in the very end of the project. Thank you.

1. Setup and data loading¶

Here we are going to load the DATA from the raw CSV and set the number to mm

from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from IPython.display import Markdown, display

pd.set_option("display.max_columns", 200)
pd.set_option("display.width", 220)
pd.set_option("display.float_format", lambda x: f"{x:,.2f}")

#Defining MM
USD_MM = 1_000_000.0

#Finding the csv in the correct path of raising error if not found
def locate_base_dir(start: Path | None = None) -> Path:
    start = start or Path.cwd()
    for path in [start, *start.parents]:
        if (path / "DATA" / "csv_export").exists():
            return path
    raise FileNotFoundError("Could not locate DATA/csv_export from the current workspace.")


#Setting numbers to MM
def to_usd_mm(df: pd.DataFrame, exclude_cols: list[str] | None = None) -> pd.DataFrame:
    out = df.copy()
    exclude_cols = set(exclude_cols or [])
    numeric_cols = [
        col for col in out.select_dtypes(include=[np.number]).columns
        if col not in exclude_cols
    ]
    out = out.astype({col: "float64" for col in numeric_cols})
    out.loc[:, numeric_cols] = out.loc[:, numeric_cols] / USD_MM
    return out


BASE_DIR = locate_base_dir()
CSV_DIR = BASE_DIR / "DATA" / "csv_export"
#Setting the outputs direction
OUT_DIR = BASE_DIR / "Mini-projects" / "Module 4" / "outputs"
OUT_DIR.mkdir(parents=True, exist_ok=True)
#Here we are cleaning the data and preping the statments
def prep_raw_statement(path: Path) -> pd.DataFrame:
    df = pd.read_csv(path, parse_dates=["date"])
    df["fiscalYear"] = pd.to_numeric(df["fiscalYear"], errors="coerce").astype("Int64")
    df = df.dropna(subset=["fiscalYear"]).copy()
    df["fiscalYear"] = df["fiscalYear"].astype(int)
    df["period"] = df["period"].astype(str).str.upper()
    df["PeriodLabel"] = "FY" + df["fiscalYear"].astype(str) + "-" + df["period"]
    return df.sort_values("date")


is_raw = prep_raw_statement(CSV_DIR / "RL_income_statement.csv")
bs_raw = prep_raw_statement(CSV_DIR / "RL_balance_sheet.csv")
cf_raw = prep_raw_statement(CSV_DIR / "RL_cash_flow.csv")

print("Loaded raw tables:")
print("Income statement:", is_raw.shape)
print("Balance sheet   :", bs_raw.shape)
print("Cash flow       :", cf_raw.shape)
#output should all be "40" for the amount of quarters we have on the csvs
Loaded raw tables:
Income statement: (40, 40)
Balance sheet   : (40, 62)
Cash flow       : (40, 48)

2. Fixing timeline¶

Ensuring the timeline of all statments are correctly organized and linked so FCF does't break it

#data cleaning and aligment of the periods
common_timeline = (
    is_raw[["date", "fiscalYear", "period", "PeriodLabel"]]
    .merge(bs_raw[["date", "fiscalYear", "period", "PeriodLabel"]], on=["date", "fiscalYear", "period", "PeriodLabel"])
    .merge(cf_raw[["date", "fiscalYear", "period", "PeriodLabel"]], on=["date", "fiscalYear", "period", "PeriodLabel"])
    .sort_values("date")
    .reset_index(drop=True)
)

model_timeline = common_timeline.iloc[1:].copy().reset_index(drop=True)
periods = model_timeline["PeriodLabel"].tolist()

display(model_timeline.head(10))
print("Modeled periods:", model_timeline.iloc[0]["PeriodLabel"], "to", model_timeline.iloc[-1]["PeriodLabel"])
print("Number of modeled quarters:", len(model_timeline))
# displaying first 10 to ensure correctness
date fiscalYear period PeriodLabel
0 2016-07-02 2017 Q1 FY2017-Q1
1 2016-10-01 2017 Q2 FY2017-Q2
2 2016-12-31 2017 Q3 FY2017-Q3
3 2017-04-01 2017 Q4 FY2017-Q4
4 2017-07-01 2018 Q1 FY2018-Q1
5 2017-09-30 2018 Q2 FY2018-Q2
6 2017-12-30 2018 Q3 FY2018-Q3
7 2018-03-31 2018 Q4 FY2018-Q4
8 2018-06-30 2019 Q1 FY2019-Q1
9 2018-09-29 2019 Q2 FY2019-Q2
Modeled periods: FY2017-Q1 to FY2026-Q3
Number of modeled quarters: 39

3. Align¶

Since FCF needs all the statments to be aligned with each other we are doing it here, making sure all index are the same across the dataframes

is_q = is_raw.set_index("PeriodLabel").loc[periods].copy()
cf_q = cf_raw.set_index("PeriodLabel").loc[periods].copy()
bs_all = bs_raw.set_index("PeriodLabel").loc[common_timeline["PeriodLabel"]].copy()
bs_q = bs_raw.set_index("PeriodLabel").loc[periods].copy()
prev_bs_q = bs_all.shift(1).loc[periods].copy()

timeline_index = model_timeline.set_index("PeriodLabel")[["date", "fiscalYear", "period"]].copy()
timeline_index.index.name = "Period"

print("Aligned shapes:")
print("is_q     :", is_q.shape)
print("bs_q     :", bs_q.shape)
print("prev_bs_q:", prev_bs_q.shape)
print("cf_q     :", cf_q.shape)
Aligned shapes:
is_q     : (39, 39)
bs_q     : (39, 61)
prev_bs_q: (39, 61)
cf_q     : (39, 47)

4. Quarterly base table¶

Here we linking all the 3 statments to a single dataframe so is all in a single dictonary to be referenced. Me and Olivia found easier to work in a single dataframe than linking 3 different tables for calculations

quarterly_inputs = timeline_index.copy()
quarterly_inputs["revenue"] = is_q["revenue"].values
quarterly_inputs["ebit"] = is_q["ebit"].values
quarterly_inputs["incomeBeforeTax"] = is_q["incomeBeforeTax"].values
quarterly_inputs["incomeTaxExpense"] = is_q["incomeTaxExpense"].values
quarterly_inputs["interestExpense"] = is_q["interestExpense"].values
quarterly_inputs["netIncome"] = is_q["netIncome"].values
quarterly_inputs["depreciationAndAmortization"] = cf_q["depreciationAndAmortization"].values
quarterly_inputs["deferredIncomeTax"] = cf_q["deferredIncomeTax"].values
quarterly_inputs["stockBasedCompensation"] = cf_q["stockBasedCompensation"].values
quarterly_inputs["changeInWorkingCapital"] = cf_q["changeInWorkingCapital"].values
quarterly_inputs["otherNonCashItems"] = cf_q["otherNonCashItems"].values
quarterly_inputs["capitalExpenditure"] = cf_q["capitalExpenditure"].values
quarterly_inputs["netDebtIssuance"] = cf_q["netDebtIssuance"].values
quarterly_inputs["netDividendsPaid"] = cf_q["netDividendsPaid"].values
quarterly_inputs["cash_begin_reported"] = prev_bs_q["cashAndCashEquivalents"].values
quarterly_inputs["cash_end_reported"] = bs_q["cashAndCashEquivalents"].values
quarterly_inputs["delta_cash_from_bs"] = quarterly_inputs["cash_end_reported"] - quarterly_inputs["cash_begin_reported"]

quarterly_inputs_usdm = to_usd_mm(quarterly_inputs, exclude_cols=["fiscalYear"])
display(quarterly_inputs_usdm.head(8).round(1))
#making sure the dataframe was correctly built
date fiscalYear period revenue ebit incomeBeforeTax incomeTaxExpense interestExpense netIncome depreciationAndAmortization deferredIncomeTax stockBasedCompensation changeInWorkingCapital otherNonCashItems capitalExpenditure netDebtIssuance netDividendsPaid cash_begin_reported cash_end_reported delta_cash_from_bs
Period
FY2017-Q1 2016-07-02 2017 Q1 1,552.00 -29.80 -33.00 -10.90 3.40 -22.00 78.00 3.00 18.00 81.00 85.00 -78.00 -33.30 -41.00 456.30 457.00 0.70
FY2017-Q2 2016-10-01 2017 Q2 1,821.00 77.80 73.00 28.00 4.10 45.00 76.00 -12.00 14.00 -244.00 110.00 -87.00 -1.10 -41.00 457.00 434.00 -23.00
FY2017-Q3 2016-12-31 2017 Q3 1,714.00 126.70 124.00 41.80 3.60 82.00 78.00 19.00 14.00 391.00 34.00 -60.00 -101.10 -42.00 434.00 928.00 494.00
FY2017-Q4 2017-04-01 2017 Q4 1,565.80 -267.50 -268.90 -64.60 1.40 -204.30 75.50 -48.90 17.60 5.60 256.80 -59.00 -8.40 -40.80 928.00 668.30 -259.70
FY2018-Q1 2017-07-01 2018 Q1 1,347.10 91.80 86.80 27.30 5.00 59.50 72.90 -14.70 21.60 182.50 12.40 -41.90 -6.20 -40.50 668.30 830.40 162.10
FY2018-Q2 2017-09-30 2018 Q2 1,664.20 195.80 191.20 47.40 4.60 143.80 73.80 -10.60 17.80 -136.20 14.20 -32.80 -8.00 -40.60 830.40 1,111.60 281.20
FY2018-Q3 2017-12-30 2018 Q3 1,641.80 191.10 186.30 268.10 4.80 -81.80 72.70 17.30 16.90 479.20 9.80 -48.30 -7.00 -40.60 1,111.60 1,175.70 64.10
FY2018-Q4 2018-03-31 2018 Q4 1,529.20 28.70 24.90 -16.40 3.80 41.30 75.80 92.10 18.20 -236.50 33.10 -38.60 -7.00 -40.70 1,175.70 1,304.60 128.90

5. Working-capital bridge¶

Here we are following the sign logic directly from the quarter-over-quarter balance sheet changes.

working_capital_bridge = timeline_index.copy()
# we create the dataframe columns
wc_cols = [
    "netReceivables",
    "inventory",
    "prepaids",
    "otherCurrentAssets",
    "accountPayables",
    "accruedExpenses",
    "deferredRevenue",
    "taxPayables",
    "otherCurrentLiabilities",
]

#function to get the true delta (unsure if this is the optimal way)
for col in wc_cols:
    working_capital_bridge[f"delta_{col}"] = bs_q[col].values - prev_bs_q[col].values

working_capital_bridge["wc_adjustment_from_bs"] = (
    -working_capital_bridge["delta_netReceivables"]
    - working_capital_bridge["delta_inventory"]
    - working_capital_bridge["delta_prepaids"]
    - working_capital_bridge["delta_otherCurrentAssets"]
    + working_capital_bridge["delta_accountPayables"]
    + working_capital_bridge["delta_accruedExpenses"]
    + working_capital_bridge["delta_deferredRevenue"]
    + working_capital_bridge["delta_taxPayables"]
    + working_capital_bridge["delta_otherCurrentLiabilities"]
)
working_capital_bridge["changeInWorkingCapital"] = cf_q["changeInWorkingCapital"].values
working_capital_bridge["wc_scope_gap"] = (
    working_capital_bridge["changeInWorkingCapital"] - working_capital_bridge["wc_adjustment_from_bs"]
)
#Display making sure is correct and years aligned
working_capital_bridge_usdm = to_usd_mm(working_capital_bridge, exclude_cols=["fiscalYear"])
display(working_capital_bridge_usdm[["fiscalYear", "period", "wc_adjustment_from_bs", "changeInWorkingCapital", "wc_scope_gap"]].head(12).round(1))
fiscalYear period wc_adjustment_from_bs changeInWorkingCapital wc_scope_gap
Period
FY2017-Q1 2017 Q1 146.90 81.00 -65.90
FY2017-Q2 2017 Q2 -13.00 -244.00 -231.00
FY2017-Q3 2017 Q3 232.00 391.00 159.00
FY2017-Q4 2017 Q4 215.30 5.60 -209.70
FY2018-Q1 2018 Q1 -60.70 182.50 243.20
FY2018-Q2 2018 Q2 -124.00 -136.20 -12.20
FY2018-Q3 2018 Q3 341.00 479.20 138.20
FY2018-Q4 2018 Q4 -171.30 -236.50 -65.20
FY2019-Q1 2019 Q1 -16.40 15.40 31.80
FY2019-Q2 2019 Q2 -319.70 -293.00 26.70
FY2019-Q3 2019 Q3 205.20 234.80 29.60
FY2019-Q4 2019 Q4 14.00 -34.20 -48.20

6. Cash flow statement build¶

Here we used the same logic from the two cells up. We ties everything that is from different dataframes into a single larger one and make the calculations that are needed to find the CFO, CFI and CFF

cash_flow_statement = timeline_index.copy()

#Creating a single dataframe to work
cash_flow_statement["netIncome"] = is_q["netIncome"].values
cash_flow_statement["depreciationAndAmortization"] = cf_q["depreciationAndAmortization"].values
cash_flow_statement["deferredIncomeTax"] = cf_q["deferredIncomeTax"].values
cash_flow_statement["stockBasedCompensation"] = cf_q["stockBasedCompensation"].values
cash_flow_statement["changeInWorkingCapital"] = cf_q["changeInWorkingCapital"].values
cash_flow_statement["otherNonCashItems"] = cf_q["otherNonCashItems"].values
cash_flow_statement["reclassifications"] = 0.0

#Making CFO calculation
cash_flow_statement["CFO"] = (
    cash_flow_statement["netIncome"]
    + cash_flow_statement["depreciationAndAmortization"]
    + cash_flow_statement["deferredIncomeTax"]
    + cash_flow_statement["stockBasedCompensation"]
    + cash_flow_statement["changeInWorkingCapital"]
    + cash_flow_statement["otherNonCashItems"]
    + cash_flow_statement["reclassifications"]
)

#Appending what is needed for CFI and making the calculations by "hand"
cash_flow_statement["capitalExpenditure"] = cf_q["capitalExpenditure"].values
cash_flow_statement["acquisitionsNet"] = cf_q["acquisitionsNet"].values
cash_flow_statement["purchasesOfInvestments"] = cf_q["purchasesOfInvestments"].values
cash_flow_statement["salesMaturitiesOfInvestments"] = cf_q["salesMaturitiesOfInvestments"].values
cash_flow_statement["otherInvestingActivities"] = cf_q["otherInvestingActivities"].values
cash_flow_statement["CFI"] = (
    cash_flow_statement["capitalExpenditure"]
    + cash_flow_statement["acquisitionsNet"]
    + cash_flow_statement["purchasesOfInvestments"]
    + cash_flow_statement["salesMaturitiesOfInvestments"]
    + cash_flow_statement["otherInvestingActivities"]
)


#Repeat for CFF
cash_flow_statement["netDebtIssuance"] = cf_q["netDebtIssuance"].values
cash_flow_statement["netCommonStockIssuance"] = cf_q["netCommonStockIssuance"].values
cash_flow_statement["netPreferredStockIssuance"] = cf_q["netPreferredStockIssuance"].values
cash_flow_statement["netDividendsPaid"] = cf_q["netDividendsPaid"].values
cash_flow_statement["otherFinancingActivities"] = cf_q["otherFinancingActivities"].values
cash_flow_statement["CFF"] = (
    cash_flow_statement["netDebtIssuance"]
    + cash_flow_statement["netCommonStockIssuance"]
    + cash_flow_statement["netPreferredStockIssuance"]
    + cash_flow_statement["netDividendsPaid"]
    + cash_flow_statement["otherFinancingActivities"]
)

#Putting everything together to find the cash delta
cash_flow_statement["cash_begin"] = prev_bs_q["cashAndCashEquivalents"].values
cash_flow_statement["cash_end_reported_bs"] = bs_q["cashAndCashEquivalents"].values
cash_flow_statement["effectOfForexChangesOnCash"] = cf_q["effectOfForexChangesOnCash"].values
cash_flow_statement["cash_definition_gap"] = (
    cash_flow_statement["cash_end_reported_bs"]
    - cash_flow_statement["cash_begin"]
    - cash_flow_statement["CFO"]
    - cash_flow_statement["CFI"]
    - cash_flow_statement["CFF"]
    - cash_flow_statement["effectOfForexChangesOnCash"]
)
#Not forgetting the other as mentioned in chapters PDF

cash_flow_statement["other_cash"] = (
    cash_flow_statement["effectOfForexChangesOnCash"]
    + cash_flow_statement["cash_definition_gap"]
)
cash_flow_statement["delta_cash"] = (
    cash_flow_statement["CFO"]
    + cash_flow_statement["CFI"]
    + cash_flow_statement["CFF"]
    + cash_flow_statement["other_cash"]
)
cash_flow_statement["cash_end"] = cash_flow_statement["cash_begin"] + cash_flow_statement["delta_cash"]

cash_flow_statement_usdm = to_usd_mm(cash_flow_statement, exclude_cols=["fiscalYear"])
display(cash_flow_statement_usdm[["fiscalYear", "period", "CFO", "CFI", "CFF", "other_cash", "delta_cash", "cash_begin", "cash_end"]].head(12).round(1))
fiscalYear period CFO CFI CFF other_cash delta_cash cash_begin cash_end
Period
FY2017-Q1 2017 Q1 243.00 -41.00 -185.60 -15.70 0.70 456.30 457.00
FY2017-Q2 2017 Q2 -11.00 28.30 -41.30 1.00 -23.00 457.00 434.00
FY2017-Q3 2017 Q3 618.00 30.00 -142.90 -11.10 494.00 434.00 928.00
FY2017-Q4 2017 Q4 102.30 -224.80 -149.10 11.90 -259.70 928.00 668.30
FY2018-Q1 2018 Q1 334.20 -128.50 -61.00 17.40 162.10 668.30 830.40
FY2018-Q2 2018 Q2 102.80 215.20 -48.80 12.00 281.20 830.40 1,111.60
FY2018-Q3 2018 Q3 514.10 -404.50 -48.90 3.40 64.10 1,111.60 1,175.70
FY2018-Q4 2018 Q4 24.00 128.70 -38.80 15.00 128.90 1,175.70 1,304.60
FY2019-Q1 2019 Q1 230.60 -827.10 -164.40 -11.40 -772.30 1,304.60 532.30
FY2019-Q2 2019 Q2 -17.60 94.30 -54.10 -1.80 20.80 532.30 553.10
FY2019-Q3 2019 Q3 470.10 -77.30 -262.60 -2.80 127.40 553.10 680.50
FY2019-Q4 2019 Q4 100.70 -69.20 -124.60 -3.30 -96.40 680.50 584.10

7. Balance-sheet shell¶

We are going to repeat the process to inlude the cash changes in the balance sheet. We create the DF make the calculations by "hand" and compute the final total values

balance_sheet_shell = timeline_index.copy()
balance_sheet_shell["cashAndCashEquivalents_model"] = cash_flow_statement["cash_end"].values
balance_sheet_shell["shortTermInvestments"] = bs_q["shortTermInvestments"].values
balance_sheet_shell["netReceivables"] = bs_q["netReceivables"].values
balance_sheet_shell["inventory"] = bs_q["inventory"].values
balance_sheet_shell["prepaids"] = bs_q["prepaids"].values
balance_sheet_shell["otherCurrentAssets"] = bs_q["otherCurrentAssets"].values
balance_sheet_shell["totalCurrentAssets_model"] = (
    balance_sheet_shell["cashAndCashEquivalents_model"]
    + balance_sheet_shell["shortTermInvestments"]
    + balance_sheet_shell["netReceivables"]
    + balance_sheet_shell["inventory"]
    + balance_sheet_shell["prepaids"]
    + balance_sheet_shell["otherCurrentAssets"]
)
balance_sheet_shell["nonCurrentAssets_reported"] = bs_q["totalAssets"].values - bs_q["totalCurrentAssets"].values
balance_sheet_shell["totalAssets_model"] = (
    balance_sheet_shell["totalCurrentAssets_model"]
    + balance_sheet_shell["nonCurrentAssets_reported"]
)
balance_sheet_shell["totalLiabilitiesAndTotalEquity"] = bs_q["totalLiabilitiesAndTotalEquity"].values
balance_sheet_shell["balance_error"] = (
    balance_sheet_shell["totalAssets_model"]
    - balance_sheet_shell["totalLiabilitiesAndTotalEquity"]
)

balance_sheet_shell_usdm = to_usd_mm(balance_sheet_shell, exclude_cols=["fiscalYear"])
display(balance_sheet_shell_usdm[["fiscalYear", "period", "cashAndCashEquivalents_model", "totalAssets_model", "totalLiabilitiesAndTotalEquity", "balance_error"]].head(12).round(1))
fiscalYear period cashAndCashEquivalents_model totalAssets_model totalLiabilitiesAndTotalEquity balance_error
Period
FY2017-Q1 2017 Q1 457.00 6,118.00 6,118.00 0.00
FY2017-Q2 2017 Q2 434.00 6,067.00 6,067.00 0.00
FY2017-Q3 2017 Q3 928.00 5,966.00 5,966.00 0.00
FY2017-Q4 2017 Q4 668.30 5,652.00 5,652.00 0.00
FY2018-Q1 2018 Q1 830.40 5,814.00 5,814.00 0.00
FY2018-Q2 2018 Q2 1,111.60 6,028.30 6,028.30 0.00
FY2018-Q3 2018 Q3 1,175.70 6,199.00 6,199.00 0.00
FY2018-Q4 2018 Q4 1,304.60 6,143.30 6,143.30 0.00
FY2019-Q1 2019 Q1 532.30 6,035.30 6,035.30 0.00
FY2019-Q2 2019 Q2 553.10 6,155.50 6,155.50 0.00
FY2019-Q3 2019 Q3 680.50 6,092.00 6,092.00 0.00
FY2019-Q4 2019 Q4 584.10 5,942.80 5,942.80 0.00

8. Integrity checks¶

Integrity checks on the basic level as mentioned on the chapter.

  • Timeline
  • roll foward error
  • Plug error
  • Checking if our numbers match the ones reported
integrity_report = timeline_index.copy()
integrity_report["cash_flow_identity_error"] = (
    cash_flow_statement["delta_cash"]
    - cash_flow_statement["CFO"]
    - cash_flow_statement["CFI"]
    - cash_flow_statement["CFF"]
    - cash_flow_statement["other_cash"]
)
integrity_report["cash_roll_forward_error"] = (
    cash_flow_statement["cash_end"]
    - cash_flow_statement["cash_begin"]
    - cash_flow_statement["delta_cash"]
)
integrity_report["cash_to_bs_error"] = (
    cash_flow_statement["cash_end"] - bs_q["cashAndCashEquivalents"].values
)
integrity_report["balance_error"] = balance_sheet_shell["balance_error"].values
integrity_report["cfo_vs_reported"] = (
    cash_flow_statement["CFO"] - cf_q["netCashProvidedByOperatingActivities"].values
)

# What I believe to be the most important part, making sure our calculations are the same values as reported in the original CF reported by RL
integrity_report["cfi_vs_reported"] = (
    cash_flow_statement["CFI"] - cf_q["netCashProvidedByInvestingActivities"].values
)
integrity_report["cff_vs_reported"] = (
    cash_flow_statement["CFF"] - cf_q["netCashProvidedByFinancingActivities"].values
)
integrity_report["delta_cash_vs_reported"] = (
    cash_flow_statement["delta_cash"] - cf_q["netChangeInCash"].values
)
integrity_report["cash_definition_gap"] = cash_flow_statement["cash_definition_gap"].values

#Tolerance for the test checks of fail
tolerance = 2.0 * USD_MM
integrity_report["passes_all_core_checks"] = (
    integrity_report[["cash_flow_identity_error", "cash_roll_forward_error", "cash_to_bs_error", "balance_error"]]
    .abs()
    .max(axis=1)
    <= tolerance
)

integrity_report_usdm = to_usd_mm(integrity_report, exclude_cols=["fiscalYear"])
display(integrity_report_usdm[["fiscalYear", "period", "cash_to_bs_error", "balance_error", "cash_definition_gap", "passes_all_core_checks"]].tail(12).round(3))
print("All quarters pass:", bool(integrity_report["passes_all_core_checks"].all()))
fiscalYear period cash_to_bs_error balance_error cash_definition_gap passes_all_core_checks
Period
FY2023-Q4 2023 Q4 0.00 0.00 -0.20 True
FY2024-Q1 2024 Q1 0.00 0.00 0.60 True
FY2024-Q2 2024 Q2 0.00 0.00 0.20 True
FY2024-Q3 2024 Q3 0.00 0.00 -1.70 True
FY2024-Q4 2024 Q4 0.00 0.00 0.10 True
FY2025-Q1 2025 Q1 0.00 0.00 1.70 True
FY2025-Q2 2025 Q2 0.00 0.00 -0.50 True
FY2025-Q3 2025 Q3 0.00 0.00 0.40 True
FY2025-Q4 2025 Q4 0.00 0.00 -0.10 True
FY2026-Q1 2026 Q1 0.00 0.00 1.40 True
FY2026-Q2 2026 Q2 0.00 1.40 1.60 True
FY2026-Q3 2026 Q3 0.00 0.00 -1.50 True
All quarters pass: True
pretax_income = is_q["incomeBeforeTax"].replace(0, np.nan)
raw_tax_rate = is_q["incomeTaxExpense"] / pretax_income
fallback_tax_rate = raw_tax_rate.replace([np.inf, -np.inf], np.nan).median()
if pd.isna(fallback_tax_rate):
    fallback_tax_rate = 0.25
tax_rate_used = raw_tax_rate.where(
    (pretax_income > 0) & raw_tax_rate.between(0.0, 0.60),
    np.nan,
).fillna(fallback_tax_rate)

fcf_outputs = timeline_index.copy()
fcf_outputs["revenue"] = is_q["revenue"].values
fcf_outputs["ebit"] = is_q["ebit"].values
fcf_outputs["tax_rate_used"] = tax_rate_used.values
fcf_outputs["NOPAT"] = (is_q["ebit"] * (1.0 - tax_rate_used)).values
fcf_outputs["depreciationAndAmortization"] = cf_q["depreciationAndAmortization"].values
fcf_outputs["other_operating_adjustments"] = (
    cf_q["deferredIncomeTax"]
    + cf_q["stockBasedCompensation"]
    + cf_q["otherNonCashItems"]
).values
fcf_outputs["netCapEx"] = (-cf_q["capitalExpenditure"]).values
fcf_outputs["delta_owc_use_of_cash"] = (-cf_q["changeInWorkingCapital"]).values
fcf_outputs["FCFF"] = (
    fcf_outputs["NOPAT"]
    + fcf_outputs["depreciationAndAmortization"]
    + fcf_outputs["other_operating_adjustments"]
    - fcf_outputs["netCapEx"]
    - fcf_outputs["delta_owc_use_of_cash"]
)
fcf_outputs["netBorrowing"] = cf_q["netDebtIssuance"].values
fcf_outputs["after_tax_interest"] = (is_q["interestExpense"] * (1.0 - tax_rate_used)).values
fcf_outputs["FCFE_from_NI_bridge"] = (
    is_q["netIncome"]
    + cf_q["depreciationAndAmortization"]
    + cf_q["deferredIncomeTax"]
    + cf_q["stockBasedCompensation"]
    + cf_q["otherNonCashItems"]
    - (-cf_q["capitalExpenditure"])
    - (-cf_q["changeInWorkingCapital"])
    + cf_q["netDebtIssuance"]
).values
fcf_outputs["FCFE_from_FCFF_bridge"] = (
    fcf_outputs["FCFF"]
    - fcf_outputs["after_tax_interest"]
    + fcf_outputs["netBorrowing"]
)
fcf_outputs["FCFE_bridge_gap"] = (
    fcf_outputs["FCFE_from_NI_bridge"] - fcf_outputs["FCFE_from_FCFF_bridge"]
)

fcf_outputs_usdm = to_usd_mm(fcf_outputs, exclude_cols=["fiscalYear", "tax_rate_used"])
display(fcf_outputs_usdm[["fiscalYear", "period", "FCFF", "FCFE_from_NI_bridge", "FCFE_from_FCFF_bridge", "FCFE_bridge_gap"]].tail(12).round(1))
fiscalYear period FCFF FCFE_from_NI_bridge FCFE_from_FCFF_bridge FCFE_bridge_gap
Period
FY2023-Q4 2023 Q4 -42.90 -53.60 -53.60 0.00
FY2024-Q1 2024 Q1 238.80 225.10 225.10 0.00
FY2024-Q2 2024 Q2 39.00 24.90 24.90 0.00
FY2024-Q3 2024 Q3 571.50 557.50 557.50 0.00
FY2024-Q4 2024 Q4 90.50 76.10 76.10 0.00
FY2025-Q1 2025 Q1 252.40 239.00 239.00 0.00
FY2025-Q2 2025 Q2 64.50 49.60 49.60 -0.00
FY2025-Q3 2025 Q3 686.20 671.50 671.50 0.00
FY2025-Q4 2025 Q4 50.40 36.80 36.80 -0.00
FY2026-Q1 2026 Q1 -2.10 481.00 481.00 0.00
FY2026-Q2 2026 Q2 -39.50 -446.10 -458.60 12.50
FY2026-Q3 2026 Q3 714.40 704.00 704.00 0.00

9. Export¶

CSV outputs for the results only for control purpose since was not specify under the project instructions

quarterly_inputs_usdm.to_csv(OUT_DIR / "chapter5_quarterly_inputs_usdm.csv")
working_capital_bridge_usdm.to_csv(OUT_DIR / "chapter5_quarterly_working_capital_bridge_usdm.csv")
cash_flow_statement_usdm.to_csv(OUT_DIR / "chapter5_quarterly_cash_flow_statement_usdm.csv")
balance_sheet_shell_usdm.to_csv(OUT_DIR / "chapter5_quarterly_balance_sheet_shell_usdm.csv")
integrity_report_usdm.to_csv(OUT_DIR / "chapter5_quarterly_integrity_report.csv")
fcf_outputs_usdm.to_csv(OUT_DIR / "chapter5_quarterly_fcf_outputs_usdm.csv")

latest_period = cash_flow_statement_usdm.index[-1]
latest_cf = cash_flow_statement_usdm.loc[latest_period]
latest_fcf = fcf_outputs_usdm.loc[latest_period]



(OUT_DIR / "chapter5_quarterly_review_summary.txt")

print("Outputs saved to:", OUT_DIR)
Outputs saved to: /Users/theomachado/Library/CloudStorage/OneDrive-UniversityofFlorida/Spring 2026/FIN4453 - OFC/Mini-projects/Module 4/outputs

TO GO ABOVE AND BEYOND¶

Decided to do a small graph showing the tragectory of CFO CFI and CFF

ax = cash_flow_statement_usdm[["CFO", "CFI", "CFF"]].plot(
    kind="bar",
    stacked=True,
    figsize=(14, 6),
    color=["#1b6ca8", "#f08a24", "#2f855a"],
)
cash_flow_statement_usdm["delta_cash"].plot(
    ax=ax,
    color="black",
    marker="o",
    linewidth=2,
    label="delta_cash",
)
ax.set_title("Quarterly CFO / CFI / CFF and delta_cash")
ax.set_xlabel("Quarter")
ax.set_ylabel("USD mm")
ax.axhline(0, color="black", linewidth=1)
ax.tick_params(axis="x", rotation=75)
ax.legend(loc="best")
plt.tight_layout()
plt.show()
No description has been provided for this image