CompareSG

How rare is your HDB flat?

Adjoined flats and Type S1 are about equally rare, 1 in 581 and 1 in 618 HDB resales, but one lives on a single street. Pick a flat model to see what it is, where it is and how many homes there really are.

Pick a flat model

Every section below changes to the model you pick. Commonest first, rarest last.

How rare is Type S1?

Each pennant is one HDB resale since January 2017. The big coloured ones are your model.

1 in 618 HDB resales were a Type S1

391 of 241,705 resales, January 2017 to September 2026

391 resales7 blocks1 town

Each pennant is one resale, 2,000 of them. The big coloured ones are the Type S1 you would expect to meet: about 3.

Roughly 1 in 618 HDB resales since January 2017 was a Type S1.

What is Type S1?

In plain words, from what HDB's file shows. Press a name in the list to read another.

A flat's type is its room count: 1-room, 2-room, 3-room, 4-room, 5-room, Executive and Multi-Generation. Its model is HDB's name for the design, and the 4-room flats in the file alone come in 12 different models. These 10 are each less than 1 in 500 resales.

Type S1

The 4-room flats in the 7 towers at blocks 1A to 1G Cantonment Road. At 50 floors, these are the tallest HDB blocks in Singapore. Type S2 is the 5-room version in the same towers.

4-room flatsTypically 94 sqmLease started 2011

See how tall these towers are

All 10 names in plain words

  • A maisonette is a home on two levels. This one is a 5-room flat, with leases starting 1983 to 1985, and it is bigger than other 5-room flats: typically 140 sqm against 117 sqm.5-room flats · Typically 140 sqm · Lease started 1983 to 1985
  • Adjoined means joined together. The file does not say which flats were joined, but these homes are bigger than other flats of the same type: a typical Adjoined 4-room flat is 118 sqm, and other 4-room flats are 93 sqm. They come as 4-room, 5-room and Executive flats, with leases starting 1967 to 1989.4-room, 5-room and Executive flats · Typically 139 sqm · Lease started 1967 to 1989
  • The 4-room flats in the 7 towers at blocks 1A to 1G Cantonment Road. At 50 floors, these are the tallest HDB blocks in Singapore. Type S2 is the 5-room version in the same towers.4-room flats · Typically 94 sqm · Lease started 2011
  • The 5-room flats in the same 7 towers, blocks 1A to 1G Cantonment Road. Type S1 is the 4-room version.5-room flats · Typically 106 sqm · Lease started 2011
  • A Premium Apartment with a loft. They come as 4-room and 5-room flats, with leases starting 2012 to 2016. A typical Loft 5-room flat is 147 sqm, against 117 sqm for other 5-room flats.4-room and 5-room flats · Typically 97 sqm · Lease started 2012 to 2016
  • HDB homes in low, two-storey blocks, not in a tall block of flats. Every Terrace resale was in a two-storey block on one of four streets: Jalan Bahagia, Jalan Ma'mor, Jalan Tenteram and Stirling Road. They come as 3-room and 4-room homes, with leases starting 1968 to 1972. A typical Terrace 3-room home is 94 sqm, against 67 sqm for other 3-room flats.3-room and 4-room flats · Typically 98 sqm · Lease started 1968 to 1972
  • An older multi-generation flat, and not the same label as 3Gen. The file gives it its own flat type, Multi-Generation, and its leases started 1987 to 1988, decades before 3Gen's 2018 to 2021.Multi-Generation flat type · Typically 164 sqm · Lease started 1987 to 1988
  • Short for three generations. HDB describes 3Gen flats as for eligible multi-generation families living under one roof. In the file they are 5-room flats, with leases starting 2018 to 2021, and the first resale was in 2022.5-room flats · Typically 120 sqm · Lease started 2018 to 2021
  • Another maisonette label (a home on two levels). In the file it is a 5-room flat, with leases starting 1983, sold in 4 blocks.5-room flats · Typically 137 sqm · Lease started 1983
  • A maisonette label again, this time for Executive flats. Leases started 1997 to 1998, and it was sold in 14 blocks in 2 towns.Executive flats · Typically 147 sqm · Lease started 1997 to 1998

Where can you find Type S1?

Every block and street with a resale since January 2017, counted once per block.

7blocks
1 of 26town
1street
Central Area391 resales

All 391 resales were at 7 blocks on one street, Cantonment Road, in Central Area.

Which rare models are easiest to find?

Number of blocks with at least one resale, since January 2017. Your pick is highlighted.

Adjoined flat322 blocks, 23 towns
Model A-Maisonette37 blocks, 9 towns
Terrace37 blocks, 2 towns
3Gen31 blocks, 9 towns
Premium Apartment Loft19 blocks, 2 towns
Multi Generation8 blocks, 3 towns
Type S17 blocks, 1 town
Type S27 blocks, 1 town
Fewer than 50 resales, not ranked
Premium Maisonette14 blocks, 2 towns
Improved-Maisonette4 blocks, 1 town

Adjoined flat sold in 322 blocks across 23 towns. Type S1, almost as rare, sold in 7 blocks on one street.

How many homes are there really?

Resales count sales, not homes. Where HDB's block list lets us count the homes, here are both.

1 in 618
resales (a flat sold twice counts twice)
1 in 904
homes (1,232 counted)

For every 100 Type S1 homes there were about 32 resales since January 2017. For the average HDB flat it is about 22.

Type S1 homes are sold about 1.5 times as often as the average flat, so it looks more common in resales than it is in homes: 1 in 618 resales, but 1 in 904 homes.

How we counted the homes: The 4-room flats in the 7 blocks 1A to 1G Cantonment Road.

How often does Type S1 come up?

Resales per year. 2020 and 2021 were affected by COVID-19: across all flats, resales peaked in 2021 at 29,087.

201741
201839
201937
202024
202172
202242
202337
202430
202538
2026 (to Sep)31

About 40 a year overall. Roughly steady: 39 a year in 2017 to 2019, 36 a year in 2024 to 2026.

Fun finds

How rare are all ten put together?
1 in 126
1,916 of 241,705 resales were one of these ten models. The other 239,789 were everything else.
How big is the biggest Terrace home?
366.7 sqm
About 4 times the typical resale flat (93 sqm). This is the floor area HDB records for one Terrace resale, and it can include a roof terrace.
How many Adjoined-flat blocks sold just once?
249 of 322
77 in every 100 blocks with an Adjoined flat have had only one resale since January 2017.

The short version

Adjoined flats (416 resales, 1 in 581) and Type S1 (391, 1 in 618) are about equally rare, but Adjoined flats sold in 322 blocks across 23 of 26 towns and Type S1 in 7 blocks on 1 street, from HDB's file of 241,705 resales from January 2017 to September 2026, downloaded 8 Oct 2026.

What we did. We looked at the 10 flat models that are each less than 1 in 500 HDB resales. Together they are 1 in 126 resales (1,916 of 241,705). For each we counted resales, blocks, towns and streets, and read what the file shows about its rooms, size and lease start to explain the name in plain words. HDB does not publish homes by model, so for 4 of the 10 we counted homes from HDB's block list.

What we found. 3Gen, Multi Generation and Terrace are the rarest of the 8 models with at least 50 resales (1 in 2,877, 1 in 2,686 and 1 in 1,965). Improved-Maisonette and Premium Maisonette have fewer than 50 resales, so they are shown but not ranked. Type S1 and Type S2 are the 4-room and 5-room flats in the 7 50-floor towers on Cantonment Road. 3Gen's leases start 2018 to 2021, and it had no resales in 2017 to 2019.

What to keep in mind. A resale count is not a count of homes. Terrace is 1 in 1,965 resales but about 1 in 3,908 homes: 285 homes, of which 43 per 100 changed hands in 9 years 9 months, against 22 per 100 for the average flat. HDB's file gives model names but no definitions, so the plain meanings come from what the file shows, apart from the everyday meanings of maisonette and adjoined, and HDB's own line on 3Gen.

By Ben (opens in a new tab). Updated . Data: HDB, Resale flat prices based on registration date from Jan-2017 onwards and HDB Property Information, data.gov.sg.

Questions people ask

What are the different HDB flat models?

The resale file lists 21 flat models. The commonest are Model A (87,206 resales), Improved (58,982 resales), New Generation (29,280 resales), Premium Apartment (26,427 resales) and Simplified (9,222 resales). The 10 rarest, each less than 1 in 500 resales, are Model A-Maisonette, Adjoined flat, Type S1, Type S2, Premium Apartment Loft, Terrace, Multi Generation, 3Gen, Improved-Maisonette and Premium Maisonette. This page explains those 10.

What is the difference between a flat type and a flat model?

The flat type is the room count the file uses: 1-room, 2-room, 3-room, 4-room, 5-room, Executive and Multi-Generation. The flat model is HDB's name for the design. The same flat type comes in many models: the 4-room flats in the file alone come in 12 different models.

What are Type S1 and Type S2 HDB flats?

The 4-room flats in the 7 towers at blocks 1A to 1G Cantonment Road. At 50 floors, these are the tallest HDB blocks in Singapore. Type S2 is the 5-room version in the same towers. Since January 2017 there have been 391 Type S1 resales (1 in 618) and 199 Type S2 resales (1 in 1,215), all at the same 7 blocks.

What does 3Gen mean for an HDB flat?

Short for three generations. HDB describes 3Gen flats as for eligible multi-generation families living under one roof. In the file they are 5-room flats, with leases starting 2018 to 2021, and the first resale was in 2022. It is rare in resales because it is new to them: no resales in 2017 to 2019 and 58 in 2024 to 2026, so far 84 in all, 1 in 2,877.

What is a Multi Generation flat, and is it the same as 3Gen?

No, the file keeps them as separate labels. An older multi-generation flat, and not the same label as 3Gen. The file gives it its own flat type, Multi-Generation, and its leases started 1987 to 1988, decades before 3Gen's 2018 to 2021. There have been 90 Multi Generation resales since January 2017 (1 in 2,686), and HDB's block list shows 367 multi-generation homes in 8 blocks.

What is an HDB Terrace house, and how many are there?

HDB homes in low, two-storey blocks, not in a tall block of flats. Every Terrace resale was in a two-storey block on one of four streets: Jalan Bahagia, Jalan Ma'mor, Jalan Tenteram and Stirling Road. They come as 3-room and 4-room homes, with leases starting 1968 to 1972. A typical Terrace 3-room home is 94 sqm, against 67 sqm for other 3-room flats. We count 285 Terrace homes: every home in the 41 two-storey blocks on those streets. HDB's block list does not name models, so this count is ours, not HDB's. Since January 2017 there have been 123 Terrace resales, 1 in 1,965 of all resales.

What is an Adjoined flat, and are Adjoined flats rare?

Adjoined means joined together. The file does not say which flats were joined, but these homes are bigger than other flats of the same type: a typical Adjoined 4-room flat is 118 sqm, and other 4-room flats are 93 sqm. They come as 4-room, 5-room and Executive flats, with leases starting 1967 to 1989. By resales they are rare: 1 in 581. But they are not hard to find: they were sold in 322 blocks across 23 of 26 towns, and 249 of those blocks had just one resale. Type S1 is almost as rare (1 in 618) but sits in 7 blocks on one street.

What are the maisonette flat models?

A maisonette is a home on two levels. The file has three maisonette labels among the rare models: Model A-Maisonette (420 resales), Improved-Maisonette (32) and Premium Maisonette (22). Model A-Maisonette is a 5-room flat, with leases starting 1983 to 1985, and it is bigger than other 5-room flats: typically 140 sqm against 117 sqm.

What is a Premium Apartment Loft?

A Premium Apartment with a loft. They come as 4-room and 5-room flats, with leases starting 2012 to 2016. A typical Loft 5-room flat is 147 sqm, against 117 sqm for other 5-room flats. There have been 139 resales since January 2017, 1 in 1,739.

What are the rarest HDB flat models?

Among the 8 models with at least 50 resales since January 2017, the rarest by share of resales are 3Gen (1 in 2,877), Multi Generation (1 in 2,686) and Terrace (1 in 1,965). Improved-Maisonette (32 resales) and Premium Maisonette (22) are rarer still, but too few to rank. A share of resales is not the same as a share of homes.

Does 1 in 1,965 mean 1 in 1,965 homes?

No. The 1 in 1,965 counts resales. Terrace homes are sold about twice as often as the average flat (43 resales per 100 homes in 9 years 9 months, against 22), so there are fewer Terrace homes than the resale share suggests: 1 in 3,908. Type S1 is sold about 1.5 times as often as the average flat (32 per 100), so 1 in 618 resales is 1 in 904 homes.

Which rare flat models are in my town?

Pick your town in the "Where can you find it?" section above. It lists every rare model with a resale in that town since January 2017.

The numbers behind it

The 10 rarest flat models in HDB resales, January 2017 to September 2026
Flat modelResalesShare of resalesBlocksTownsStatus
Model A-Maisonette4201 in 575379Ranked
Adjoined flat4161 in 58132223Ranked
Type S13911 in 61871Ranked
Type S21991 in 1,21571Ranked
Premium Apartment Loft1391 in 1,739192Ranked
Terrace1231 in 1,965372Ranked
Multi Generation901 in 2,68683Ranked
3Gen841 in 2,877319Ranked
Improved-Maisonette321 in 7,55341Fewer than 50 resales, not ranked
Premium Maisonette221 in 10,987142Fewer than 50 resales, not ranked
The 10 rare flat models in plain words: what the file shows
Flat modelFlat typeTypical size (sqm)Leases startedWhere
Model A-Maisonette5-room1401983 to 198537 blocks in 9 towns
Adjoined flat4-room, 5-room and Executive1391967 to 1989322 blocks in 23 towns
Type S14-room9420117 blocks on Cantonment Road
Type S25-room10620117 blocks on Cantonment Road
Premium Apartment Loft4-room and 5-room972012 to 201619 blocks in 2 towns
Terrace3-room and 4-room981968 to 197237 blocks in 2 towns
Multi GenerationMulti-Generation1641987 to 19888 blocks in 3 towns
3Gen5-room1202018 to 202131 blocks in 9 towns
Improved-Maisonette5-room13719834 blocks in 1 town
Premium MaisonetteExecutive1471997 to 199814 blocks in 2 towns
All 21 flat models in HDB resales, January 2017 to September 2026
Flat modelResalesShare of resalesFlat types in the file
Model A87,2061 in 32-room, 3-room, 4-room and 5-room
Improved58,9821 in 41-room, 2-room, 3-room, 4-room and 5-room
New Generation29,2801 in 83-room and 4-room
Premium Apartment26,4271 in 92-room, 3-room, 4-room, 5-room and Executive
Simplified9,2221 in 263-room and 4-room
Apartment8,6321 in 28Executive
Maisonette6,5981 in 37Executive
Standard6,3981 in 382-room, 3-room, 4-room and 5-room
DBSS3,8231 in 632-room, 3-room, 4-room and 5-room
Model A22,7081 in 894-room
2-room5131 in 4712-room
Model A-Maisonette4201 in 5755-room
Adjoined flat4161 in 5814-room, 5-room and Executive
Type S13911 in 6184-room
Type S21991 in 1,2155-room
Premium Apartment Loft1391 in 1,7394-room and 5-room
Terrace1231 in 1,9653-room and 4-room
Multi Generation901 in 2,686Multi-Generation
3Gen841 in 2,8775-room
Improved-Maisonette321 in 7,5535-room
Premium Maisonette221 in 10,987Executive
Resales against homes, for the four models where HDB's block list lets us count homes
Flat modelHomes countedShare of homesShare of resalesResales per 100 homesAgainst the average flat
Type S11,2321 in 9041 in 61832about 1.5 times as often as
Type S26161 in 1,8081 in 1,21532about 1.5 times as often as
Terrace2851 in 3,9081 in 1,96543about twice as often as
Multi Generation3671 in 3,0351 in 2,68625about as often as
Average HDB flat1,113,907--22-
Resales per year, by flat model (2026 is January to September)
Flat model2017201820192020202120222023202420252026
Model A-Maisonette31334637625539503532
Adjoined flat31424649504043403738
Type S141393724724237303831
Type S223241618422311151413
Premium Apartment Loft12426431310101218
Terrace12121415191371489
Multi Generation41514818123646
3Gen00000521211918
Improved-Maisonette5622325322
Premium Maisonette2143130143
All flats20,50921,56122,18623,33329,08726,72025,75427,83225,08419,639

The boring bit

Where the data comes from

HDB's Resale flat prices based on registration date from Jan-2017 onwards, on data.gov.sg: 242,259 rows, downloaded 8 Oct 2026. We used the 241,705 from January 2017 to September 2026. For homes we used HDB's HDB Property Information, on data.gov.sg: 13,392 block records and 1,113,907 flats sold by HDB.

What is not claimed

  • A resale is not a home. HDB gives a block and street but no unit, so one flat sold twice counts twice. Only 4 models have a count of homes, and those counts are ours (see below).
  • The resale file leaves out sales that may not reflect the full market price, such as sales between relatives and sales of part shares (HDB's own note), so every share here is a share of those resales.
  • Homes counted: Type S1 is the 4-room flats and Type S2 the 5-room flats in the 7 blocks 1A to 1G Cantonment Road (every resale in those blocks is S1 or S2). Terrace is every home in the 41 two-storey blocks on Jalan Bahagia, Jalan Ma'mor, Jalan Tenteram and Stirling Road (all 37 blocks with a Terrace resale are two-storey blocks there, and no other model was sold in those 41 blocks). Multi Generation is the multi-generation units HDB lists in 8 blocks. HDB's block list does not name flat models, so these are our matches.
  • Adjoined flat, Premium Apartment Loft, Model A-Maisonette, 3Gen and the two other maisonette models sit inside blocks with other flats, so their homes cannot be counted from the block list.
  • What the names mean: HDB's file lists model names but does not define them. The plain words on this page come from what the file shows (flat type, floor area, lease start, number of floors, streets). "Typical size" is the middle (median) resale. Two meanings are everyday word meanings, not HDB definitions: a maisonette is a home on two levels, and adjoined means joined together. The 3Gen line follows HDB's own description of 3Gen flats on its Types of flats page (hdb.gov.sg), read 8 Oct 2026.
  • Premium Apartment Loft and 3Gen have only recently started to resell, so their shares reflect a young model.
  • The window is January 2017 to September 2026, which includes 2020 and 2021, years affected by COVID-19. No figure on this page is for a single year.
  • Models with fewer than 50 resales are shown but not ranked.
  • Model names are HDB's own. "Multi Generation" and "3Gen" are separate labels in the file, so we keep them separate.
  • HDB's block list says it runs to 31 Dec 2025, yet it lists 77 residential blocks completed in 2026. We use it as published.
  • This says nothing about how good, valuable or comfortable any flat model is.

Coverage

Every resale from January 2017 to September 2026 was read: 241,705 of 241,705. The file also holds 554 rows for October 2026, a month not yet finished, which we left out. Every record in the block list was read: 13,392 of 13,392.

Report an error. Corrections are logged publicly with a date.

How we did it
FilteringGroupingBlock matchingNormalisationCross-check
  1. Downloaded HDB's two files from data.gov.sg on 8 Oct 2026: 242,259 resale rows and 13,392 block records.
  2. Kept January 2017 to September 2026: 241,705 resales. The 554 rows for October 2026, a month not yet finished, are left out.
  3. Counted each rare model: resales, blocks (a block number on a street), towns, streets, and resales per year.
  4. Ranked only models with at least 50 resales. The ten models are those that are each less than 1 in 500 resales.
  5. Counted homes where the block list allows it: Type S1 and S2 (the 4-room and 5-room flats in blocks 1A to 1G Cantonment Road), Terrace (every home in the two-storey blocks on the four Terrace streets) and Multi Generation (the multi-generation units the list shows). The code stops if any resale of those models is outside those blocks.
  6. Counted twice. The prep script counts with pandas and the page counts again with plain Python; the build stops if any model's resales, blocks, towns or resales per year, or any count of homes, differs.
Show the Python code
"""Rebuilds this insight's numbers from HDB's two public files and writes data/flat_models.json.

    python prep/prep_flat_models.py                 download both files from data.gov.sg, then rebuild
    python prep/prep_flat_models.py --no-download   rebuild from the two files already in data/raw/ (no download)

Files (in data/raw/):
    ResaleFlatPrices2017.csv      Resale flat prices based on registration date from Jan-2017 onwards (data.gov.sg)
    HDBPropertyInformation.csv    HDB Property Information (data.gov.sg)
Also written: data/dataset_metadata.json (data.gov.sg's own record of each file: name, last updated, coverage end).
Window: every resale from Jan 2017 to Sep 2026. Oct 2026 is a month not yet finished, so it is left out.
A model is ranked only if it has at least THRESH resales; the others are shown but not ranked.
The page counts the same numbers again with plain Python (page.py); build_page.py stops if the two disagree.
"""
import json, math, re, sys, time, urllib.request
from pathlib import Path
import pandas as pd

HERE = Path(__file__).resolve().parent
DATA = HERE.parent / "data"
RAW = DATA / "raw"
DATASETS = {"resale": ("d_8b84c4ee58e3cfc0ece0d773c8ca6abc", "ResaleFlatPrices2017.csv"),
            "property": ("d_17f5382f26140b1fdae0ba2ef6239d2f", "HDBPropertyInformation.csv")}
THRESH = 50
LAST = "2026-09"
MODELS = ["Model A-Maisonette", "Adjoined flat", "Type S1", "Type S2", "Premium Apartment Loft", "Terrace",
          "Multi Generation", "3Gen", "Improved-Maisonette", "Premium Maisonette"]
ABBR = {"AVE": "Avenue", "ST": "Street", "RD": "Road", "DR": "Drive", "CRES": "Crescent", "CTRL": "Central", "STH": "South",
        "NTH": "North", "BT": "Bukit", "CL": "Close", "GDN": "Garden", "GDNS": "Gardens", "JLN": "Jalan", "LOR": "Lorong",
        "PL": "Place", "TER": "Terrace", "UPP": "Upper", "TG": "Tanjong", "KG": "Kampong", "C'WEALTH": "Commonwealth",
        "HTS": "Heights", "PK": "Park", "LK": "Link", "BLVD": "Boulevard", "MKT": "Market", "CTR": "Centre"}
SPECIAL = {"MCNAIR": "McNair", "MA'MOR": "Ma'mor", "QUEEN'S": "Queen's"}


def get(url):
    # data.gov.sg refuses requests that carry no User-Agent (HTTP 403), so send a plain one
    req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 (CompareSG insight prep)"})
    with urllib.request.urlopen(req, timeout=120) as r:
        return r.read()


def download():
    keep_all = {}
    RAW.mkdir(parents=True, exist_ok=True)
    for key, (ds, fname) in DATASETS.items():
        meta = json.loads(get("https://api-production.data.gov.sg/v2/public/api/datasets/" + ds + "/metadata"))["data"]
        keep_all[key] = {k: meta.get(k) for k in ("datasetId", "name", "managedBy", "lastUpdatedAt", "coverageStart", "coverageEnd", "format")}
        for _ in range(20):
            j = json.loads(get("https://api-open.data.gov.sg/v1/public/api/datasets/" + ds + "/poll-download"))["data"]
            if j.get("status") == "DOWNLOAD_SUCCESS":
                break
            time.sleep(3)
        (RAW / fname).write_bytes(get(j["url"]))
    (DATA / "dataset_metadata.json").write_text(json.dumps(keep_all, indent=1), encoding="utf-8")


if "--no-download" not in sys.argv:
    download()


def ru(x):
    return int(math.floor(x + 0.5))


def pretty(s):
    out = []
    for w in str(s).split():
        out.append(ABBR.get(w) or SPECIAL.get(w) or (w if any(c.isdigit() for c in w) else w.capitalize()))
    return " ".join(out)


def town(t):
    return {"KALLANG/WHAMPOA": "Kallang/Whampoa"}.get(t, t.title())


r_all = pd.read_csv(RAW / "ResaleFlatPrices2017.csv")
raw_rows = len(r_all)
r = r_all[r_all.month <= LAST].copy()
N = len(r)
months = (2026 - 2017) * 12 + 9
years = months / 12
r["town"] = r.town.map(town)
r["street"] = r.street_name.map(pretty)
r["blk"] = r.block.astype(str)
r["key"] = r.blk + "|" + r.street
r["flat_type"] = r.flat_type.str.title().str.replace(" Room", " room", regex=False)
r["year"] = r.month.str[:4].astype(int)
YRS = [str(y) for y in range(2017, 2027)]
all_per_year = [int((r.year == y).sum()) for y in range(2017, 2027)]
assert sum(all_per_year) == N
towns_all = sorted(r.town.unique())

p = pd.read_csv(RAW / "HDBPropertyInformation.csv")
sold_cols = [c for c in p.columns if c.endswith("_sold")]
p["sold"] = p[sold_cols].sum(axis=1)
stock_total = int(p.sold.sum())
p["street_p"] = p.street.map(pretty)
p["blk"] = p.blk_no.astype(str)
p["key"] = p.blk + "|" + p.street_p

avg_per_home = N / stock_total   # resales per home over the whole window, for the average flat

out = {"N": N, "months": months, "years": years, "stock_total": stock_total, "last_full": LAST, "thresh": THRESH, "yrs": YRS,
       "all_per_year": all_per_year, "median_area_all": float(r.floor_area_sqm.median()), "raw_rows": raw_rows,
       "oct_rows": int((r_all.month > LAST).sum()), "prop_rows": len(p), "n_towns_all": len(towns_all), "towns_all": towns_all,
       "avg_per_home": avg_per_home}

# ---- exact duplicates (same row twice) : counted, not removed, because HDB gives no unit number ----
dup = r.duplicated(subset=["month", "town", "flat_type", "blk", "street_name", "storey_range", "floor_area_sqm", "flat_model",
                           "lease_commence_date", "resale_price"], keep=False)
out["dups_all"] = int(dup.sum())

models = []
for name in MODELS:
    x = r[r.flat_model == name]
    n = len(x)
    g = x.groupby("key")
    blocks = g.ngroups
    blocks_once = int((g.size() == 1).sum())
    # ties are broken by name, so the order does not depend on the pandas version
    tl = x.groupby("town").agg(n=("key", "size"), b=("key", "nunique"))
    tl = tl.loc[sorted(tl.index, key=lambda t: (-tl.n[t], -tl.b[t], t))]
    sl = x.groupby("street").size()
    sl = sl.loc[sorted(sl.index, key=lambda t: (-sl[t], t))]
    py = [int((x.year == y).sum()) for y in range(2017, 2027)]
    first3, last3 = sum(py[:3]), sum(py[-3:])
    ramp = (last3 / (33 / 12)) / max(first3 / 3, 0.0001) > 3 or first3 == 0
    types = sorted(x.flat_type.unique(), key=lambda s: (s == "Executive", s))
    # same-type comparison: median area of this model vs every other resale of the same flat type
    cmp = []
    for t in types:
        a = x[x.flat_type == t].floor_area_sqm
        b = r[(r.flat_type == t) & (r.flat_model != name)].floor_area_sqm
        if len(a) >= 20 and len(b) >= 20:
            cmp.append([t, float(a.median()), float(b.median()), int(len(a))])
    m = dict(name=name, n=int(n), one_in=N / n, blocks=int(blocks), blocks_once=blocks_once, towns=int(len(tl)), streets=int(len(sl)),
             town_list=[[t, int(v.n), int(v.b)] for t, v in tl.iterrows()], street_list=[[s, int(v)] for s, v in sl.items()],
             first3=first3, last3=last3, ramp=bool(ramp), types=types, area=[float(x.floor_area_sqm.min()), float(x.floor_area_sqm.max())],
             area_med=float(x.floor_area_sqm.median()), lease=[int(x.lease_commence_date.min()), int(x.lease_commence_date.max())],
             per_year=py, first_year=int(x.year.min()), ranked=bool(n >= THRESH), cmp=cmp, stock=None)
    models.append(m)
byname = {m["name"]: m for m in models}

# ---- homes ("stock"), only where HDB's block list lets us count them ----
# Type S1 and S2: blocks 1A-1G Cantonment Road
pin = p[(p.street_p == "Cantonment Road") & p.blk.isin(list("1" + c for c in "ABCDEFG"))]
assert len(pin) == 7 and (pin.residential == "Y").all()
pin_keys = set(pin.key)
rp = r[r.key.isin(pin_keys)]
assert set(rp.flat_model) <= {"Type S1", "Type S2"}, set(rp.flat_model)
assert set(r[r.flat_model.isin(["Type S1", "Type S2"])].key) == pin_keys
s1_homes, s2_homes = int(pin["4room_sold"].sum()), int(pin["5room_sold"].sum())
assert int(pin[["1room_sold", "2room_sold", "3room_sold", "exec_sold", "multigen_sold", "studio_apartment_sold"]].sum().sum()) == 0
assert set(rp[rp.flat_model == "Type S1"].flat_type) == {"4 room"} and set(rp[rp.flat_model == "Type S2"].flat_type) == {"5 room"}
out["pin_blocks"] = 7
out["pin_floors"] = int(pin.max_floor_lvl.max())
assert (pin.max_floor_lvl == out["pin_floors"]).all()
out["pin_tallest_in_file"] = bool(out["pin_floors"] == p[p.residential == "Y"].max_floor_lvl.max())
# Terrace: every home in two-storey residential blocks on the four streets that have Terrace resales
tr = r[r.flat_model == "Terrace"]
tstreets = sorted(tr.street.unique())
tp = p[(p.street_p.isin(tstreets)) & (p.residential == "Y") & (p.max_floor_lvl == 2)]
assert set(tr.key) <= set(tp.key), "a Terrace resale is outside the two-storey blocks"
assert set(p[p.key.isin(set(tr.key))].max_floor_lvl) == {2}
t_homes = int(tp.sold.sum())
other_in_tp = set(r[r.key.isin(set(tp.key))].flat_model) - {"Terrace"}
out["terrace_stock_blocks"] = int(len(tp))
out["terrace_streets"] = tstreets
out["terrace_other_models_in_those_blocks"] = sorted(other_in_tp)
out["terrace_towns"] = sorted(tr.town.unique())
out["terrace_max_area"] = float(tr.floor_area_sqm.max())
# Multi Generation: units HDB lists as multigen
mgp = p[p.multigen_sold > 0]
mg = r[r.flat_model == "Multi Generation"]
assert set(mg.key) <= set(mgp.key), "a Multi Generation resale is outside the blocks that list multigen units"
mg_homes = int(mgp.multigen_sold.sum())
out["mg_blocks"] = int(len(mgp))
for name, homes in [("Type S1", s1_homes), ("Type S2", s2_homes), ("Terrace", t_homes), ("Multi Generation", mg_homes)]:
    m = byname[name]
    m["stock"] = homes
    m["stock_one_in"] = stock_total / homes
    m["per_home"] = m["n"] / homes
    m["x_avg"] = m["per_home"] / avg_per_home

# ---- extras used in copy ----
out["all_rare_n"] = int(sum(m["n"] for m in models))
out["n_models_all"] = int(r.flat_model.nunique())
out["flat_types"] = sorted(r.flat_type.unique())
out["blocks_2026"] = int(((p.year_completed == 2026) & (p.residential == "Y")).sum())
out["n_models_4room"] = int(r[r.flat_type == "4 room"].flat_model.nunique())
am = r.groupby("flat_model").agg(n=("key", "size"), t=("flat_type", lambda s: sorted(set(s))))
am = am.loc[sorted(am.index, key=lambda k: (-am.n[k], k))]
out["all_models"] = [[k, int(v.n), v.t] for k, v in am.iterrows()]
out["models"] = models
out["adj_once_pct"] = ru(100 * byname["Adjoined flat"]["blocks_once"] / byname["Adjoined flat"]["blocks"])
json.dump(out, open(DATA / "flat_models.json", "w"), indent=1, default=float)
print("N", N, "stock_total", stock_total, "dups", out["dups_all"])
for m in models:
    print(f"{m['name']:24} n={m['n']:5} 1in{m['one_in']:8.0f} blocks={m['blocks']:4} towns={m['towns']:2} streets={m['streets']:3} types={m['types']} area={m['area']} med={m['area_med']} lease={m['lease']} stock={m['stock']} cmp={m['cmp']}")
print("terrace stock blocks", out["terrace_stock_blocks"], t_homes, "other models in those blocks", other_in_tp)
print("pin floors", out["pin_floors"], "tallest in file", out["pin_tallest_in_file"], "S1/S2 homes", s1_homes, s2_homes, "mg", mg_homes, out["mg_blocks"])
print("models total", out["n_models_all"], "4room models", out["n_models_4room"])

What could make it wrong: a model name or block entered wrongly in HDB's files, a block that holds a mix of models (which would make a count of homes too high), or a plain-words line that does not match how HDB itself describes a model. The two separate counts agree on every model, so the counting is not the weak point; the files are.

No machine learning was needed. The questions were "how many" and "where", which counting answers directly.

Source: HDB, Resale flat prices based on registration date from Jan-2017 onwards (opens in a new tab) and HDB Property Information (opens in a new tab), data.gov.sg, downloaded 8 Oct 2026. Contains information from data.gov.sg made available under the Singapore Open Data Licence.