SPB Git

spb/wp5_uqo Public

UQO Working Paper No. 5 — Airbnb, residential rents and housing market pressure.

TeX 53.4% Python 46.5%
5.6 KB · 178 lines python
Raw Blame History
1# Author: Simon-Pierre Boucher — contact@spboucher.ai2"""301_inspect_raw_data.py4----------------------5Load both raw datasets (data/raw/airbnb.csv, data/raw/rent.json), print6inspection summaries, and save a data-dictionary / inspection log to7results/logs/data_inspection.txt.89Note: the raw files are not distributed with the repository (see10data/raw/README.md). This step is only needed to rebuild the processed11parquet files from scratch; results/logs/data_inspection.txt already12contains its output from the original run.13"""1415import json16import sys17from io import StringIO18from pathlib import Path1920sys.path.insert(0, str(Path(__file__).resolve().parent.parent))2122import pandas as pd2324from src.config import AIRBNB_RAW, RENT_RAW, LOG_DIR, require2526RAW_HINT = ("Place the original raw files in data/raw/ to run steps 01-03. "27            "The processed parquet files in data/processed/ already allow "28            "running steps 04-10.")2930LOG_PATH = LOG_DIR / "data_inspection.txt"3132# ── helper to capture + print simultaneously ─────────────────────────────────33log_buf = StringIO()343536def log(msg: str = "") -> None:37    """Print to stdout and buffer for the log file."""38    print(msg)39    log_buf.write(msg + "\n")404142def inspect_airbnb() -> None:43    """Inspect the raw Airbnb CSV: dtypes, nulls, summaries, value counts."""44    log("=" * 80)45    log("AIRBNB DATASET  —  airbnb.csv")46    log("=" * 80)4748    ab = pd.read_csv(AIRBNB_RAW)4950    log(f"\nShape: {ab.shape[0]} rows x {ab.shape[1]} columns\n")5152    # dtypes53    log("--- Dtypes ---")54    for col in ab.columns:55        log(f"  {col:30s}  {str(ab[col].dtype)}")5657    # nulls58    log("\n--- Null counts ---")59    nulls = ab.isnull().sum()60    for col in ab.columns:61        n = nulls[col]62        pct = 100 * n / len(ab)63        log(f"  {col:30s}  {n:6d}  ({pct:5.1f}%)")6465    # numeric summary66    log("\n--- Numeric summary ---")67    log(ab.describe().to_string())6869    # key categorical value counts70    for col in ["city", "property_type", "price_category", "rating_category"]:71        log(f"\n--- Value counts: {col} ---")72        vc = ab[col].value_counts()73        for val, cnt in vc.items():74            log(f"  {str(val):50s}  {cnt}")7576    # boolean columns77    for col in ["is_superhost", "is_guest_favorite", "pets_allowed"]:78        log(f"\n--- Value counts: {col} ---")79        vc = ab[col].value_counts()80        for val, cnt in vc.items():81            log(f"  {str(val):10s}  {cnt}")8283    # sample rows84    log("\n--- First 5 rows ---")85    log(ab.head().to_string())868788def inspect_rent() -> None:89    """Inspect the raw Realtor.ca JSON: structure, missingness, patterns."""90    log("\n" + "=" * 80)91    log("RENT DATASET  —  rent.json")92    log("=" * 80)9394    with open(RENT_RAW, "r", encoding="utf-8") as f:95        rent_raw = json.load(f)9697    log(f"\nTotal records: {len(rent_raw)}")9899    # Flatten key fields into a DataFrame for inspection100    rows = []101    for rec in rent_raw:102        prop = rec.get("Property", {})103        addr = prop.get("Address", {})104        bld = rec.get("Building", {})105        rows.append({106            "address_text": addr.get("AddressText", ""),107            "latitude": addr.get("Latitude", ""),108            "longitude": addr.get("Longitude", ""),109            "property_type": prop.get("Type", ""),110            "lease_rent": prop.get("LeaseRent", ""),111            "lease_rent_unformatted": prop.get("LeaseRentUnformattedValue", ""),112            "building_type": bld.get("Type", ""),113            "bedrooms": bld.get("Bedrooms", ""),114            "bathrooms_total": bld.get("BathroomTotal", ""),115            "size_interior": bld.get("SizeInterior", ""),116            "stories_total": bld.get("StoriesTotal", ""),117            "postal_code": rec.get("PostalCode", ""),118            "province": rec.get("ProvinceName", ""),119            "scraped_at": rec.get("scrapedAt", ""),120        })121122    rt = pd.DataFrame(rows)123124    log(f"Shape (flattened): {rt.shape[0]} rows x {rt.shape[1]} columns\n")125126    # dtypes127    log("--- Dtypes ---")128    for col in rt.columns:129        log(f"  {col:30s}  {str(rt[col].dtype)}")130131    # blanks / nulls132    log("\n--- Empty-string + null counts ---")133    for col in rt.columns:134        n_null = rt[col].isnull().sum()135        n_blank = (rt[col] == "").sum()136        total_missing = n_null + n_blank137        pct = 100 * total_missing / len(rt)138        log(f"  {col:30s}  null={n_null:5d}  blank={n_blank:5d}  "139            f"total={total_missing:5d}  ({pct:5.1f}%)")140141    # value counts for key categoricals142    for col in ["property_type", "building_type"]:143        log(f"\n--- Value counts: {col} ---")144        vc = rt[col].value_counts()145        for val, cnt in vc.items():146            log(f"  {str(val):40s}  {cnt}")147148    # lease_rent patterns149    log("\n--- LeaseRent frequency patterns (top 10) ---")150    patterns = rt["lease_rent"].str.extract(r"(\$[\d,]+/\w+)")[0].value_counts().head(10)151    for val, cnt in patterns.items():152        log(f"  {val:30s}  {cnt}")153154    # check rent period distribution155    log("\n--- LeaseRent period distribution ---")156    for period in ["Monthly", "Yearly", "Weekly"]:157        cnt = rt["lease_rent"].str.contains(period, na=False).sum()158        log(f"  {period:15s}  {cnt}")159160    # sample rows161    log("\n--- First 5 rows (flattened) ---")162    log(rt.head().to_string())163164165def main() -> None:166    require(AIRBNB_RAW, RAW_HINT)167    require(RENT_RAW, RAW_HINT)168169    inspect_airbnb()170    inspect_rent()171172    LOG_PATH.write_text(log_buf.getvalue(), encoding="utf-8")173    log(f"\n>>> Inspection log saved to {LOG_PATH}")174175176if __name__ == "__main__":177    main()178