Monday, 24 August 2026

Forecasting Airline Passengers with Triple Exponential Smoothing (Holt-Winters)

Time series forecasting is one of the most practical skills in data science. In this short post, I’ll walk you through a classic example using Triple Exponential Smoothing (also known as the Holt-Winters method) on the famous Airline Passengers dataset.

What is Triple Exponential Smoothing?Simple exponential smoothing only captures the level of a series.
Double exponential smoothing adds a trend component.
Triple exponential smoothing goes one step further — it also captures seasonality
This makes it especially useful for data that shows:
  • A clear trend (upward or downward)
  • Repeating seasonal patterns (e.g., yearly cycles)
The method works by maintaining three equations:
  • One for the level
  • One for the trend
  • One for the seasonal component
The DatasetWe used the classic International Airline Passengers dataset (monthly totals from January 1949 to December 1960). This series is perfect for demonstration because it has:
  • A strong upward trend
  • Clear yearly seasonality (more passengers in summer, fewer in winter)
  • Increasing seasonal amplitude over time
Approach
  1. Split the data
    • Training set: All data except the last 24 months
    • Test set: The final 24 months (used to evaluate performance)
  2. Fit a Holt-Winters model with:
    • Multiplicative trend
    • Multiplicative seasonality
    • Seasonal period = 12 (yearly pattern)
  3. Generate forecasts:
    • Forecast the 24-month test period
    • Extend the forecast another 5 years into the future



ResultsThe model captured both the rising trend and the seasonal peaks/troughs reasonably well. On the test set it achieved solid accuracy (typically around 5–8% MAPE depending on the exact run).When we extended the forecast five years ahead, the model continued the upward trajectory while preserving the yearly seasonal pattern — exactly what we would expect from this type of data.




Python Code by Grok

# ============================================================
# Triple Exponential Smoothing (Holt-Winters)
# Classic Airline Passengers + 5-Year Future Forecast
# ============================================================

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from statsmodels.tsa.holtwinters import ExponentialSmoothing
from sklearn.metrics import mean_absolute_error, mean_squared_error

# ------------------------------------------------------------
# 1. Load the classic Airline Passengers data
# ------------------------------------------------------------
url = "https://raw.githubusercontent.com/jbrownlee/Datasets/master/airline-passengers.csv"
data = pd.read_csv(url, parse_dates=['Month'], index_col='Month')
data.columns = ['Passengers']
data = data.asfreq('MS')   # Monthly frequency

print("Data shape:", data.shape)
print(data.head())
print(data.tail())

# ------------------------------------------------------------
# 2. Train / Test Split
# ------------------------------------------------------------
train = data.iloc[:-24]   # All data except last 24 months
test  = data.iloc[-24:]   # Last 24 months (2 years)

print(f"\nTrain size : {len(train)} months")
print(f"Test size  : {len(test)} months")

# ------------------------------------------------------------
# 3. Build the Holt-Winters model
# ------------------------------------------------------------
"""
Parameters:
- trend='mul'           → multiplicative trend
- seasonal='mul'        → multiplicative seasonality
- seasonal_periods=12   → yearly seasonality (monthly data)
"""

model = ExponentialSmoothing(
    train['Passengers'],
    trend='mul',
    seasonal='mul',
    seasonal_periods=12,
    damped_trend=False
)

# ------------------------------------------------------------
# 4. Fit the model
# ------------------------------------------------------------
fitted_model = model.fit(optimized=True)

print("\n===== Model Summary =====")
print(fitted_model.summary())

# ------------------------------------------------------------
# 5. Forecast
# ------------------------------------------------------------
# Forecast for the test period (24 months)
forecast_test = fitted_model.forecast(len(test))

# Forecast additional 5 years (60 months) into the future
future_steps = 60
forecast_future = fitted_model.forecast(len(test) + future_steps)

# Extract only the future 5-year part
forecast_5years = forecast_future[-future_steps:]

# Create datetime index for the 5-year forecast
last_date = test.index[-1]
future_index = pd.date_range(
    start=last_date + pd.offsets.MonthBegin(1),
    periods=future_steps,
    freq='MS'
)

# ------------------------------------------------------------
# 6. Evaluation (on test set only)
# ------------------------------------------------------------
mae  = mean_absolute_error(test['Passengers'], forecast_test)
rmse = np.sqrt(mean_squared_error(test['Passengers'], forecast_test))
mape = np.mean(np.abs((test['Passengers'] - forecast_test) / test['Passengers'])) * 100

print(f"\nTest MAE  : {mae:.2f}")
print(f"Test RMSE : {rmse:.2f}")
print(f"Test MAPE : {mape:.2f}%")

# ------------------------------------------------------------
# 7. Main Plot (Train + Test + Forecast on Test + 5-Year Future)
# ------------------------------------------------------------
plt.figure(figsize=(15, 6))

plt.plot(train.index, train['Passengers'], label='Train', color='steelblue')
plt.plot(test.index,  test['Passengers'],  label='Test (Actual)', color='green')

plt.plot(test.index, forecast_test, label='Forecast (Test period)',
         color='red', linestyle='--', linewidth=2)

plt.plot(future_index, forecast_5years, label='Forecast (Next 5 years)',
         color='darkorange', linestyle='--', linewidth=2)

plt.title('Triple Exponential Smoothing (Holt-Winters)\nAirline Passengers + 5-Year Future Forecast')
plt.xlabel('Date')
plt.ylabel('Passengers (thousands)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

# ------------------------------------------------------------
# 8. In-sample Fit Plot
# ------------------------------------------------------------
plt.figure(figsize=(14, 5))
plt.plot(train.index, train['Passengers'], label='Actual (Train)')
plt.plot(train.index, fitted_model.fittedvalues, label='Fitted values', alpha=0.85)
plt.title('In-sample Fit of Holt-Winters')
plt.xlabel('Date')
plt.ylabel('Passengers (thousands)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()



Friday, 21 August 2026

Detecting Time Series Stationarity using (Classic Air Passengers) Dataset from Kaggle

A stationary time series is one whose statistical properties do not change over time. Its mean, variance, and autocorrelation structure remain constant no matter which period you examine.

Weak Stationarity (the practical definition)A series is weakly stationary if it meets three conditions:
  • Constant mean: The average value does not depend on time.
  • Constant variance: The spread around the mean stays the same.
  • Constant autocorrelation: The correlation between a value and its lagged versions depends only on the lag, not on the time point.
Why it mattersMost classical forecasting models (AR, MA, ARMA, ARIMA) assume stationarity. Non-stationary data can produce unreliable forecasts and misleading statistical results. This is why we use tests such as ADF and KPSS, and apply transformations (log, differencing) when needed.Quick visual check
  • Stationary: Fluctuates around a fixed level with roughly constant amplitude.
  • Non-stationary: Shows a clear trend, changing variance, or shifting seasonal patterns.
Example: The classic AirPassengers series is non-stationary due to its upward trend and increasing variance. After log-transform and differencing, it becomes much closer to stationary.


ADF and KPSS Tests

Two common statistical tests help us check stationarity:
  • ADF (Augmented Dickey-Fuller) Test
    Null hypothesis: the series is non-stationary.
    A small p-value (< 0.05) means we reject the null → the series is stationary.
  • KPSS Test
    Null hypothesis: the series is stationary.
    A small p-value (< 0.05) means we reject the null → the series is non-stationary.
Using both tests together gives a more reliable conclusion. If they agree that the series is non-stationary, we know we need to transform it before further analysis.

Python Code from Grok

# ============================================================
# AirPassengers – Download from Kaggle + ADF & KPSS tests + Plot
# ============================================================

import os
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.tsa.stattools import adfuller, kpss
import warnings
from statsmodels.tools.sm_exceptions import InterpolationWarning
warnings.filterwarnings("ignore", category=InterpolationWarning)

# ------------------------------------------------------------
# 1. Authentication (choose ONE of the methods below)
# ------------------------------------------------------------

# Method A – Recommended: set environment variables
# (replace with your real values)
os.environ['KAGGLE_USERNAME'] = 'YOUR_KAGGLE_USERNAME'
os.environ['KAGGLE_KEY']      = 'YOUR_KAGGLE_API_KEY'   # ← paste your key here later

# Method B – Alternative: put kaggle.json in ~/.kaggle/
# (the file should contain: {"username":"...", "key":"..."})

# ------------------------------------------------------------
# 2. Download the dataset from Kaggle
# ------------------------------------------------------------
import kagglehub

# This is a clean, public dataset that contains the classic AirPassengers.csv
dataset_path = kagglehub.dataset_download("ashfakyeafi/air-passenger-data-for-time-series-analysis")

print(f"Dataset downloaded to: {dataset_path}")

# The file is usually named AirPassengers.csv
csv_path = os.path.join(dataset_path, "AirPassengers.csv")
df = pd.read_csv(csv_path)

print("\nRaw columns:", df.columns.tolist())
print(df.head())

# ------------------------------------------------------------
# 3. Prepare the time series
# ------------------------------------------------------------
# The CSV usually has columns like 'Month' and '#Passengers' (or similar)
# Adjust the column names if necessary after inspecting the print above

# Common column names in this dataset:
date_col = 'Month' if 'Month' in df.columns else df.columns[0]
value_col = '#Passengers' if '#Passengers' in df.columns else df.columns[1]

df[date_col] = pd.to_datetime(df[date_col])
df = df.set_index(date_col)
ts = df[value_col].astype(float).sort_index()

print("\nPrepared time series:")
print(ts.head())
print(f"Length: {len(ts)} observations")
print(f"Date range: {ts.index.min()} → {ts.index.max()}")

# ------------------------------------------------------------
# 4. Plot the series to visually show non-stationarity
# ------------------------------------------------------------
plt.figure(figsize=(12, 5))
ts.plot(title='AirPassengers – Visual Evidence of Non-Stationarity\n(Upward trend + increasing variance)')
plt.ylabel('Passengers (thousands)')
plt.xlabel('Year')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

# ------------------------------------------------------------
# 5. Stationarity test helper
# ------------------------------------------------------------
def check_stationarity(series, name="Series"):
    print("\n" + "="*65)
    print(f"Stationarity Tests → {name}")
    print("="*65)

    series = series.dropna()

    # ----- ADF -----
    adf_result = adfuller(series, autolag='AIC')
    adf_stat, adf_p = adf_result[0], adf_result[1]

    print("\nADF Test")
    print(f"  Statistic : {adf_stat:10.4f}")
    print(f"  p-value   : {adf_p:10.4f}")
    if adf_p < 0.05:
        print("  → Reject H0 → STATIONARY")
    else:
        print("  → Fail to reject H0 → NON-STATIONARY")

    # ----- KPSS -----
    kpss_result = kpss(series, regression='c', nlags='auto')
    kpss_stat, kpss_p = kpss_result[0], kpss_result[1]

    print("\nKPSS Test")
    print(f"  Statistic : {kpss_stat:10.4f}")
    # Handle the case where p-value is reported as 0.01 but is actually smaller
    if kpss_p == 0.01:
        print(f"  p-value   : < 0.01   (actual p-value is smaller than 0.01)")
    else:
        print(f"  p-value   : {kpss_p:10.4f}")

    if kpss_p < 0.05:
        print("  → Reject H0 → NON-STATIONARY")
    else:
        print("  → Fail to reject H0 → STATIONARY")

    # Combined conclusion
    print("\nCombined conclusion:")
    adf_stat_bool = adf_p < 0.05
    kpss_stat_bool = kpss_p >= 0.05

    if adf_stat_bool and kpss_stat_bool:
        print("  Both tests agree → Series is STATIONARY")
    elif not adf_stat_bool and not kpss_stat_bool:
        print("  Both tests agree → Series is NON-STATIONARY")
    else:
        print("  Tests disagree → further investigation recommended")

# ------------------------------------------------------------
# 6. Run the tests
# ------------------------------------------------------------
check_stationarity(ts, name="Original AirPassengers (from Kaggle)")

# Optional: also test a transformed version
ts_log_diff = np.log(ts).diff().dropna()
check_stationarity(ts_log_diff, name="Log + First Difference")

Wednesday, 19 August 2026

Decomposition of Time Series (Classic Air Passengers) Dataset from Kaggle

The Air Passengers dataset on Kaggle (https://www.kaggle.com/datasets/rakannimer/air-passengers) records monthly international airline passenger totals from January 1949 to December 1960 (in thousands). This classic Box & Jenkins series is a popular teaching example because it clearly displays trend and seasonality in just 144 observations.

What Is Time Series Data?Time series data consists of observations collected at regular time intervals. The order matters—each value depends on previous ones. Examples include stock prices, monthly sales, and the passenger counts in this dataset. The key feature is temporal dependence, which allows us to detect patterns and make forecasts.

Time Series DecompositionDecomposition splits a series into underlying components for easier analysis. A series
Y_t
can be modeled as:
  • Additive:
    Y_t = T_t + S_t + R_t
  • Multiplicative:
    Y_t = T_t \times S_t \times R_t
(The Air Passengers data usually fits a multiplicative model better, as seasonal swings grow with the overall level.)Main Components
  1. Trend – Long-term upward or downward movement (clear growth in air travel here).
  2. Seasonality – Regular repeating patterns (summer peaks and winter drops every year).
  3. Residual – Remaining random variation after trend and seasonality are removed.
Decomposing the series reveals these drivers and guides better forecasting models. This compact dataset remains one of the best starting points for learning time series analysis.

Python Code from Grok

# ============================================================
# Air Passengers Time Series Decomposition
# Dataset: rakannimer/air-passengers  (or ashfakyeafi version)
# ============================================================

import os
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.seasonal import seasonal_decompose
from kaggle.api.kaggle_api_extended import KaggleApi

# ---------- 1. Authenticate ----------
# Use a NEW Kaggle token (revoke the old one you shared earlier)
os.environ['KAGGLE_USERNAME'] = 'xxxxxxxx'
os.environ['KAGGLE_KEY'] = 'xxxxxxxxxxxxxxxxxxx'

api = KaggleApi()
api.authenticate()

# ---------- 2. Download the dataset ----------
dataset = "rakannimer/air-passengers"          # classic version
# Alternative: "ashfakyeafi/air-passenger-data-for-time-series-analysis"

download_path = "./air_passengers"
os.makedirs(download_path, exist_ok=True)

api.dataset_download_files(dataset, path=download_path, unzip=True)
print("Dataset downloaded successfully!\n")

# ---------- 3. Load and prepare the data ----------
# The file is usually named AirPassengers.csv
csv_file = os.path.join(download_path, "AirPassengers.csv")

# Fallback if the name is slightly different
if not os.path.exists(csv_file):
    csv_files = [f for f in os.listdir(download_path) if f.endswith('.csv')]
    csv_file = os.path.join(download_path, csv_files[0])
    print(f"Using file: {csv_files[0]}")

df = pd.read_csv(csv_file)
print("Raw data preview:")
print(df.head())
print("\nColumns:", df.columns.tolist())

# Standardize column names
df.columns = ['Month', 'Passengers']   # rename if needed

# Convert Month to datetime and set as index
df['Month'] = pd.to_datetime(df['Month'])
df.set_index('Month', inplace=True)
df = df.asfreq('MS')                   # Monthly Start frequency

print("\nPrepared time series:")
print(df.head(12))
print(f"\nTotal observations: {len(df)}")

# ---------- 4. Plot the original series ----------
plt.figure(figsize=(12, 5))
plt.plot(df['Passengers'], label='Air Passengers')
plt.title('Monthly International Airline Passengers (1949–1960)')
plt.xlabel('Year')
plt.ylabel('Number of Passengers (in thousands)')
plt.grid(True, alpha=0.3)
plt.legend()
plt.tight_layout()
plt.show()

# ---------- 5. Time Series Decomposition ----------
# Multiplicative model is better for this dataset
# (seasonal amplitude increases with the trend)
decomposition = seasonal_decompose(
    df['Passengers'],
    model='multiplicative',   # try 'additive' as comparison
    period=12                 # yearly seasonality
)

# ---------- 6. Plot the components ----------
fig = decomposition.plot()
fig.set_size_inches(12, 9)
fig.suptitle('Air Passengers – Time Series Decomposition\n(Trend + Seasonal + Residual)', fontsize=14, y=1.02)
plt.tight_layout()
plt.show()

# Optional: access the individual components
trend = decomposition.trend
seasonal = decomposition.seasonal
residual = decomposition.resid

print("\nDecomposition completed successfully!")
print("You can now use: trend, seasonal, residual")

Monday, 17 August 2026

Today Learn Python OOP Class and Account Receivable Aging Report

Object-Oriented Programming (OOP) is a useful way to organise a Python accounting program. Instead of putting all calculations into separate functions, we can create a class that represents an accounting process.

In our example, the ReceivablesReport class represents an Accounts Receivable report. It stores customer balances and provides methods to calculate total receivables, current balances, overdue balances and the percentage of overdue debts.

Class and Instance

A class can be viewed as a blueprint. For example:

class ReceivablesReport:
...

The class contains the accounting methods.

An instance is an actual object created from that class:

report = ReceivablesReport(customers)

Here, report is an instance containing the actual customer receivable data.

Accounts Receivable Aging Report

An Accounts Receivable Aging Report is an important accounting report used to monitor money owed by customers. It helps the accounting department identify which customers have outstanding balances and how long those balances have been unpaid.

When a company makes credit sales, the amount owed by customers is recorded as trade receivables (accounts receivable). Not all customers will pay on the same date, so the aging report classifies outstanding balances according to how overdue they are.

The accounting workflow can therefore be viewed as:

Customer Invoices
Outstanding Balances
Accounts Receivable
Classify by Age
┌─────────────────────────┐
│ Current │
│ 1–30 days overdue │
│ 31–60 days overdue │
│ Over 60 days overdue │
└─────────────────────────┘
Aging Analysis
Collection Action
Management Decision



The Accounts Receivable Aging Report is therefore more than just a list of unpaid invoices.
It gives management a clearer picture of cash collection risk and the quality of the company's receivables.
Python can then be used to automate the calculations and produce the report regularly.


Friday, 14 August 2026

Today Learn Python Tuples for a Simple Tax Analyzer Report




Python tuples can be useful for storing simple accounting records in a structured way. In this example, each expense is stored as a tuple containing four items:

("Entertainment", 8000, 4000, "Entertainment")

The structure is:

(Expense Name, Accounting Amount, Tax Deductible, Category)

Several expenses can then be stored together:

expenses = (
    ("Staff Salary", 50000, 50000, "Operating"),
    ("Office Rental", 24000, 24000, "Operating"),
    ("Entertainment", 8000, 4000, "Entertainment"),
    ("Income Tax", 12000, 0, "Tax"),
)

Python can process these tuples to calculate the deductible and non-deductible portions of the expenses.

From Net Profit to Adjusted Income

The tax analyzer starts with Net Profit from the financial 'Profit and Loss' statement.

For example:

Net Profit                  RM32,000
Add: Non-Deductible         RM29,000
                            --------
Adjusted Income             RM61,000

The basic idea is that certain expenses recorded in the financial accounts may not be deductible for tax purposes. These amounts are therefore added back to accounting profit to arrive at a simplified Adjusted Income (Adjusted Income is only for tax report, financial report 'Profit and Loss' statement only come to net profit).

The Python program automatically performs this calculation and produces:

The overall workflow is:

Accounting Expenses
        ↓
     Tuples
        ↓
 Python Tax Analyzer
        ↓
     Net Profit
        ↓
Tax Adjustments
        ↓
  Adjusted Income

This example demonstrates how a simple Python data structure such as a tuple can be combined with accounting knowledge to automate repetitive tax-analysis work.



Python code by Chatgpt

# ============================================================

# ACCOUNTING & TAX ANALYZER

# Net Profit to Adjusted Income

# Using Python Tuples

# ============================================================


import pandas as pd

import matplotlib.pyplot as plt



# ------------------------------------------------------------

# 1. ACCOUNTING EXPENSE DATA

# ------------------------------------------------------------

# Tuple structure:

# (Expense Name, Accounting Amount, Tax Deductible, Category)


expenses = (

    ("Staff Salary", 50000, 50000, "Operating"),

    ("Office Rental", 24000, 24000, "Operating"),

    ("Audit Fee", 5000, 5000, "Professional"),

    ("Entertainment", 8000, 4000, "Entertainment"),

    ("Income Tax", 12000, 0, "Tax"),

    ("Staff Training", 6000, 6000, "Training"),

    ("Donation", 3000, 0, "Donation"),

    ("Depreciation", 10000, 0, "Depreciation"),

)



# ------------------------------------------------------------

# 2. NET PROFIT

# ------------------------------------------------------------

# Assume net profit is obtained from the financial statements.


net_profit = 32000



# ------------------------------------------------------------

# 3. TOTAL ACCOUNTING EXPENSES

# ------------------------------------------------------------


total_expenses = sum(

    amount

    for name, amount, deductible, category in expenses

)



# ------------------------------------------------------------

# 4. TOTAL TAX-DEDUCTIBLE EXPENSES

# ------------------------------------------------------------


total_deductible = sum(

    deductible

    for name, amount, deductible, category in expenses

)



# ------------------------------------------------------------

# 5. NON-DEDUCTIBLE EXPENSES

# ------------------------------------------------------------


total_non_deductible = sum(

    amount - deductible

    for name, amount, deductible, category in expenses

)



# ------------------------------------------------------------

# 6. ADJUSTED INCOME

# ------------------------------------------------------------

# Simplified tax adjustment:

#

# Adjusted Income =

# Net Profit + Non-Deductible Expenses


adjusted_income = (

    net_profit + total_non_deductible

)



# ------------------------------------------------------------

# 7. CREATE DATAFRAME

# ------------------------------------------------------------


df = pd.DataFrame(

    expenses,

    columns=[

        "Expense",

        "Accounting Amount",

        "Tax Deductible",

        "Category"

    ]

)


df["Non-Deductible"] = (

    df["Accounting Amount"]

    - df["Tax Deductible"]

)



# ------------------------------------------------------------

# 8. DISPLAY EXPENSE ANALYSIS

# ------------------------------------------------------------


print("=" * 60)

print("          ACCOUNTING & TAX ANALYZER")

print("=" * 60)


display(df)


# ------------------------------------------------------------

# 14. FINAL REPORT

# ------------------------------------------------------------


print("\n" + "=" * 60)

print("              TAX REPORT")

print("=" * 60)


print(f"""

Net Profit                  RM {net_profit:,.2f}


Add: Non-Deductible

Expenses                    RM {total_non_deductible:,.2f}


Adjusted Income             RM {adjusted_income:,.2f}

""")


print("=" * 60)

print("Analysis completed.")

print("=" * 60)


Wednesday, 12 August 2026

Today Learn Python 'Generator' feature for Accounting 'Profit & Loss' Report

Python can be useful in accounting not only for calculations, but also for automating repetitive financial reporting tasks. One useful Python feature is the generator, which can produce data one item at a time using the yield statement.





Understanding the accounting calculation for P&L Statement

The report follows a simple income statement structure.

1. Sales

Sales represent the revenue earned from the company's normal business activities.

2. Cost of Goods Sold (COGS)

COGS represents the direct cost associated with producing or purchasing the goods sold.

3. Gross Profit

Gross Profit = Sales − COGS

Gross profit shows how much remains after covering the direct cost of the goods sold.

4. Other Income

Other income includes income that is not part of the company's main sales activities, such as interest income or certain miscellaneous income.

5. Expenses

Operating expenses may include salaries, rental, utilities, administration and other business expenses.

6. Net Profit

The final calculation is:

Net Profit = Gross Profit + Other Income − Expenses

The resulting figure gives a simple measure of the company's profit for each month.

Conclusion

This example demonstrates how Python programming and accounting concepts can work together. The generator handles the repetitive monthly calculations, while the accounting logic converts Sales, COGS, Other Income and Expenses into Gross Profit and Net Profit.



Python Code by ChatGPT


# ============================================

# Financial Report using Python Generator

# January - June 2026

# ============================================


# Sample accounting data

monthly_data = [

    ("January 2026",   120000, 70000,  5000, 25000),

    ("February 2026",  135000, 78000,  4000, 27000),

    ("March 2026",     150000, 85000,  6000, 30000),

    ("April 2026",     142000, 80000,  4500, 29000),

    ("May 2026",       160000, 90000,  7000, 32000),

    ("June 2026",      175000, 95000,  8000, 35000)

]



# ============================================

# Generator Function

# ============================================


def financial_report(data):


    for month, sales, cogs, other_income, expenses in data:


        # Calculate Gross Profit

        gross_profit = sales - cogs


        # Calculate Net Profit

        net_profit = gross_profit + other_income - expenses


        # Calculate margins

        gross_margin = (gross_profit / sales) * 100

        net_margin = (net_profit / sales) * 100


        # Generate one month's report

        yield {

            "Month": month,

            "Sales": sales,

            "COGS": cogs,

            "Gross Profit": gross_profit,

            "Other Income": other_income,

            "Expenses": expenses,

            "Net Profit": net_profit,

            "Gross Margin": gross_margin,

            "Net Margin": net_margin

        }



# ============================================

# Create Generator

# ============================================


report = financial_report(monthly_data)



# ============================================

# Display Financial Report

# ============================================


print("=" * 115)

print("                    FINANCIAL PERFORMANCE REPORT")

print("                         JANUARY - JUNE 2026")

print("=" * 115)


print(

    f"{'Month':<15}"

    f"{'Sales':>12}"

    f"{'COGS':>12}"

    f"{'Gross Profit':>15}"

    f"{'Other Income':>15}"

    f"{'Expenses':>12}"

    f"{'Net Profit':>15}"

)


print("-" * 115)



# Generator produces one month at a time

for row in report:


    print(

        f"{row['Month']:<15}"

        f"RM{row['Sales']:>10,.0f}"

        f"RM{row['COGS']:>10,.0f}"

        f"RM{row['Gross Profit']:>13,.0f}"

        f"RM{row['Other Income']:>13,.0f}"

        f"RM{row['Expenses']:>10,.0f}"

        f"RM{row['Net Profit']:>13,.0f}"

    )


print("=" * 115)

Monday, 10 August 2026

Today Learn Simple Python Package to create a Simple Invoice Function

 

What is a Python Package?


A Python package is a way of organizing related Python code into a structured collection of modules. Instead of putting all the functions into one large Python file, we can separate them into smaller files according to their purpose.

For example, in our simple accounting project, we create an accounting package containing functions for calculating an invoice.

InvoiceProject/
│
├── accounting/              ← Python package
│   ├── __init__.py          ← Package entry point
│   ├── invoice.py           ← Invoice calculations
│   ├── tax.py               ← Tax calculation
│   └── total.py             ← Total calculation
│
├── invoice_report.py        ← Combines the calculations
│
└── invoice_demo.ipynb       ← Jupyter Notebook





The advantage is that each module has a specific responsibility, making the code easier to understand, maintain, reuse, and expand.

How the package works

The architecture of our invoice application can be illustrated as follows:

                 User Input
              Quantity / Price
                 Tax Rate
                     │
                     ▼
            ┌─────────────────┐
            │ invoice_report  │
            │     .py         │
            └────────┬────────┘
                     │
                     ▼
            ┌─────────────────┐
            │   accounting    │
            │     package     │
            └────────┬────────┘
                     │
          ┌──────────┼──────────┐
          ▼          ▼          ▼
     ┌─────────┐ ┌─────────┐ ┌─────────┐
     │invoice  │ │   tax   │ │  total  │
     │  .py    │ │   .py   │ │   .py   │
     └────┬────┘ └────┬────┘ └────┬────┘
          │           │           │
          ▼           ▼           ▼
       Subtotal       Tax        Total
          │           │           │
          └───────────┼───────────┘
                      ▼
             ┌─────────────────┐
             │ Jupyter Notebook│
             └────────┬────────┘
                      ▼
                Invoice Report

In simple terms

Think of a Python package like an accounting department in a company.

  • invoice.py → handles invoice calculations
  • tax.py → handles tax calculations
  • total.py → calculates the final amount
  • __init__.py → connects the modules and makes them available as a package
  • invoice_report.py → coordinates the calculations
  • Jupyter Notebook → presents the final result

This approach allows us to break a large program into smaller, manageable components, while each component can be reused in other programs.

In short: A Python package is a structured way to organize and reuse related Python modules and functions.