From the General Ledger to the Management Income Statement

First lesson of the Financial Analysis and Management Control course, free to read: about 15 minutes, 3 exercises and 3 quiz questions. This is the catalogue's base lesson; in the student space, it is rewritten for your profile.

Objectives

  • Group accounting entries into management line items based on the account number
  • Build a monthly income statement with pandas
  • Calculate gross margin, value added, EBE and operating income in a simplified version

Chart of accounts and line itemsIncome statementIntermediate management balances

The Starting Point: Entries

Accounting records each transaction in accounts. In France, the general chart of accounts (PCG) organizes these accounts into classes: class 6 groups expenses, class 7 groups revenue. The first two digits specify the nature:

Prefix Nature
70 Sales (revenue)
60 Purchases
61, 62 External services (rent, fees, telecoms…)
63 Taxes and duties
64 Personnel costs
68 Depreciation, amortization and provisions

A management controller doesn’t present hundreds of accounts to management: they group them into readable line items. To do so, they rely on a written, shared mapping table that can be reviewed and audited.

The entries in this course are simplified: one positive amount per account per month, with no stock variation or closing entries.

Building the Mapping Table

MAPPING = {
    "70": "Chiffre d'affaires",
    "60": "Achats consommés",
    "61": "Charges externes",
    "62": "Charges externes",
    "63": "Impôts et taxes",
    "64": "Charges de personnel",
    "68": "Dotations aux amortissements",
}

def rubrique(account):
    return MAPPING.get(str(account)[:2], "Hors compte de résultat")

entries["rubrique"] = entries["account"].map(rubrique)

The default value "Hors compte de résultat" (“outside the income statement”) catches balance sheet accounts (class 5 for cash, class 4 for third parties…) that have no place in an income statement. If it shows up in your results, it is a signal to investigate, not a line to ignore.

The Monthly Income Statement

pnl = entries.pivot_table(index="rubrique", columns="month", values="amount", aggfunc="sum", fill_value=0)

One row per line item, one column per month: this is the expected shape of a monthly report. An essential check: the sum of pnl must equal the sum of the entries in scope. A difference means an entry was lost or counted twice.

Intermediate Balances, in a Simplified Version

The income statement is read as a cascade. Each balance removes one category of expenses and answers a question:

def sig(m):          # m: dictionary of line item -> amount for one month
    marge_brute = m["Chiffre d'affaires"] - m["Achats consommés"]
    valeur_ajoutee = marge_brute - m["Charges externes"]
    ebe = valeur_ajoutee - m["Impôts et taxes"] - m["Charges de personnel"]
    resultat_exploitation = ebe - m["Dotations aux amortissements"]
    return {"marge_brute": marge_brute, "valeur_ajoutee": valeur_ajoutee,
            "ebe": ebe, "resultat_exploitation": resultat_exploitation}

print(sig(pnl["2026-01"].to_dict()))
# gross margin 30,000, value added 26,000, EBE 10,500, operating income 8,500
  • The gross margin (here a trading margin, since the company resells goods) tells you what is left after paying for the products sold.
  • Value added measures the wealth created by the company after its external consumption.
  • The EBE (gross operating surplus, excédent brut d’exploitation) measures what operations generate before depreciation and amortization, financial costs and tax. It is a closely watched indicator because it depends little on investment and financing choices.
  • Operating income deducts the wear of investments (depreciation and amortization charges).

The official definition of the intermediate management balances includes other items (production, stock variation, operating subsidies, reversals, other income and expenses); this course keeps their structure for analysis.

Relating to Revenue

A balance is compared from one month to the next as a percentage of revenue: an EBE rate of 21% in January and 26.7% in February shows that fixed costs (personnel, rent) were better absorbed by higher revenue. This is what is called operating leverage.

Key Takeaways

  • Class 6 accounts are expenses, class 7 accounts are revenue; the prefixes give the nature.
  • A documented mapping table turns entries into line items.
  • Balances are computed as a cascade and read as a percentage of revenue.

Exercises

Solutions and tests stay on the server. In the student space, your code is run and checked, and the tutor gives hints before the solution.

  1. CodeDifficulty 1Python in the browser2 hints

    From the ledger to line items

    Write rubrique(account) which returns the line item for an account number based on its first two digits (the lesson’s table; "Hors compte de résultat" by default). Add the rubrique column to entries, then build pnl: a line item × month pivot table of the amounts.

    import pandas as pd
    
    entries = pd.DataFrame({
        "month":   ["2026-01"] * 7 + ["2026-02"] * 7,
        "account": ["707000", "607000", "613200", "626000", "635100", "641000", "681100"] * 2,
        "amount":  [50000, 20000, 3000, 1000, 500, 15000, 2000,
                    60000, 24000, 3000, 1500, 500, 15000, 2000],
    })
    
    
    def rubrique(account):
        ...
    
    
    # pnl = ...
  2. CodeDifficulty 2Python in the browser2 hints

    Intermediate balances

    Write sig(m) where m is a dictionary of line item → amount for one month. It returns marge_brute, valeur_ajoutee, ebe and resultat_exploitation. Then compute ebe_rate, a Series (index = month) of the EBE rate (EBE / revenue).

    import pandas as pd
    
    entries = pd.DataFrame({
        "month":   ["2026-01"] * 7 + ["2026-02"] * 7,
        "account": ["707000", "607000", "613200", "626000", "635100", "641000", "681100"] * 2,
        "amount":  [50000, 20000, 3000, 1000, 500, 15000, 2000,
                    60000, 24000, 3000, 1500, 500, 15000, 2000],
    })
    
    MAPPING = {
        "70": "Chiffre d'affaires",
        "60": "Achats consommés",
        "61": "Charges externes",
        "62": "Charges externes",
        "63": "Impôts et taxes",
        "64": "Charges de personnel",
        "68": "Dotations aux amortissements",
    }
    
    
    def rubrique(account):
        return MAPPING.get(str(account)[:2], "Hors compte de résultat")
    
    
    entries["rubrique"] = entries["account"].map(rubrique)
    pnl = entries.pivot_table(index="rubrique", columns="month", values="amount", aggfunc="sum", fill_value=0)
    
    
    def sig(m):
        ...
    
    
    # ebe_rate = ...
  3. Multiple choiceDifficulty 11 hint

    Reading the EBE

    A company buys an expensive machine depreciated over 5 years. Which balance is NOT affected by the depreciation charge for this machine?

    • Operating income
    • The EBE
    • Net income
    • None, all are affected

End-of-lesson quiz

  1. Which category does account 641000 belong to?

    • Revenue
    • Personnel costs
    • Cash
  2. Revenue 100, purchases 40, external expenses 15, taxes 2, personnel 25. What is the EBE?

    • 45
    • 18
    • 60
  3. Why provide a default line item “Hors compte de résultat”?

    • To hide awkward entries
    • To spot balance sheet accounts or accounts not covered by the mapping
    • Because pandas requires it

The rest of the course

  1. Budget and Variance Analysis18 min
  2. Cash Flow Forecasting18 min
  3. Product Profitability: Contribution Margin and Break-Even Point18 min
  4. Financial Dashboard and Year-End Forecast15 min

Financial Analysis and Management Control · Intermediate · 1 h 24 min