thaman / scripts /prepare_v12_features.py
Turki Almurahhem
THAMAN dual-city AVM β€” Stack v22 (NYC) + v12 (Riyadh)
2e8a71f
Raw
History Blame Contribute Delete
3.57 kB
"""
scripts/prepare_v12_features.py
================================
Enriches features_v5.csv with 7 new Overture POI categories β†’ features_v6.csv
New feature columns (500m count):
poi_atm_500m, poi_urgent_care_500m, poi_cinema_500m,
poi_library_500m, poi_childcare_500m, poi_beauty_500m, poi_hotel_500m
Uses pre-extracted CSV files from process_nyc_overture_qol.py
(much faster than re-reading 425K overture_places.geojson).
Run:
cd /Users/totam/Desktop/new_try
python scripts/prepare_v12_features.py
"""
import numpy as np
import polars as pl
import pandas as pd
from pathlib import Path
from sklearn.neighbors import BallTree
BASE = Path(__file__).resolve().parent.parent
RAW = BASE / "data" / "raw"
PROC = BASE / "data" / "processed"
INPUT = PROC / "features_v5.csv"
OUTPUT = PROC / "features_v6.csv"
RADIUS_M = 500
RADIUS_RAD = RADIUS_M / 6_371_000 # Earth radius in metres
# New POI categories: (column_name, csv_file)
NEW_POIS = [
("atm", RAW / "nyc_overture_atms.csv"),
("urgent_care", RAW / "nyc_overture_urgent_care.csv"),
("cinema", RAW / "nyc_overture_cinemas.csv"),
("library", RAW / "nyc_overture_libraries.csv"),
("childcare", RAW / "nyc_overture_childcare.csv"),
("beauty", RAW / "nyc_overture_beauty_salons.csv"),
("hotel", RAW / "nyc_overture_hotels.csv"),
]
def count_within_radius(poi_lats: np.ndarray, poi_lons: np.ndarray,
query_lats: np.ndarray, query_lons: np.ndarray) -> np.ndarray:
"""Return count of POIs within RADIUS_M for each query point."""
if len(poi_lats) == 0:
return np.zeros(len(query_lats), dtype=np.int32)
poi_rad = np.radians(np.column_stack([poi_lats, poi_lons]))
qry_rad = np.radians(np.column_stack([query_lats, query_lons]))
tree = BallTree(poi_rad, metric="haversine")
counts = tree.query_radius(qry_rad, r=RADIUS_RAD, count_only=True)
return counts.astype(np.int32)
print(f"Loading {INPUT.name} …")
df = pl.read_csv(INPUT)
print(f" {len(df):,} rows Γ— {len(df.columns)} cols")
lats = df["latitude"].to_numpy()
lons = df["longitude"].to_numpy()
new_series = []
for col_name, csv_path in NEW_POIS:
feat_col = f"poi_{col_name}_500m"
# Skip if already present
if feat_col in df.columns:
print(f" {feat_col}: already present β€” skip")
continue
if not csv_path.exists():
print(f" {feat_col}: {csv_path.name} not found β€” zeros")
new_series.append(pl.Series(feat_col, np.zeros(len(df), dtype=np.int32)))
continue
poi_df = pd.read_csv(csv_path)
poi_lats = poi_df["lat"].to_numpy(dtype=float)
poi_lons = poi_df["lon"].to_numpy(dtype=float)
# Drop NaN coordinates
mask = ~(np.isnan(poi_lats) | np.isnan(poi_lons))
poi_lats, poi_lons = poi_lats[mask], poi_lons[mask]
counts = count_within_radius(poi_lats, poi_lons, lats, lons)
new_series.append(pl.Series(feat_col, counts))
print(f" {feat_col}: {len(poi_lats):,} POIs | "
f"median={np.median(counts):.0f} max={counts.max()}")
df = df.with_columns(new_series)
print(f"\nSaving β†’ {OUTPUT.name}")
df.write_csv(OUTPUT)
print(f" Shape: {len(df):,} rows Γ— {len(df.columns)} cols")
# Sanity check
print("\nNew column stats:")
for col_name, _ in NEW_POIS:
feat_col = f"poi_{col_name}_500m"
if feat_col in df.columns:
arr = df[feat_col].to_numpy()
print(f" {feat_col:30s} median={np.median(arr):.1f} "
f"p90={np.percentile(arr, 90):.0f} max={arr.max()}")