Mundarija (23)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Koordinatalar va WGS84
- 2.2. Masofa: Evklid, haversine, lokal proyeksiya
- 2.3. Fazoviy indekslash
- 2.4. Nuqta-poligon va fazoviy birlashtirish
- 2.5. Fazoviy belgilar
- 2.6. Fazoviy avtokorrelyatsiya va Moran I
- 2.7. Fazoviy kross-validatsiya
- 2.8. Issiq nuqtalar: DBSCAN haversine bilan
- 2.9. Tuzoqlar
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Masofa: Evklid xatosi, haversine va lokal proyeksiya
- Misol 2 — Fazoviy indekslash: panjara, geohash va BallTree
- Misol 3 — Nuqta-poligon, fazoviy belgilar va maqsadli kodlashdagi sizish
- Misol 4 — Fazoviy avtokorrelyatsiya, fazoviy CV va DBSCAN issiq nuqtalar
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
28.9-dars: Geografik ma'lumot
28-QISM — MAXSUS MAVZULAR · 9-dars
1. Kirish va motivatsiya
Oldingi ikki darsda agent qaror qabul qilib, dunyoni o'zgartirdi. Endi yana ma'lumotga qaytamiz — lekin o'ziga xos turdagi ma'lumotga: joylashuv. Taksi buyurtmasi, yetkazib berish manzili, do'kon, bankomat, telefon signali, ko'chmas mulk e'loni — bularning hammasida kenglik va uzunlik bor. Birinchi qarashda ular oddiy ikki son ustun: lat va lon. Aslida esa ular kursda ko'rgan boshqa ustunlarning birortasiga o'xshamaydi.
Birinchidan, masofa noto'g'ri hisoblanadi, agar koordinatalarni tekis deb olsangiz: Toshkent kengligida bir gradus uzunlik 83.5 km, bir gradus kenglik esa 111.2 km. Gradus farqlaridan Evklid masofasi sharq-g'arb yo'nalishini uchdan bir marta oshirib yuboradi. Ikkinchidan, qidiruv sekin: "shu nuqtadan 300 m ichidagi barcha buyurtmalar" savoli har safar million nuqtani ko'rib chiqishni talab qilmasligi uchun maxsus indeks kerak. Uchinchidan — va eng muhimi — yaqin nuqtalar bir-biriga o'xshaydi. Bu fazoviy avtokorrelyatsiya: bir mahalladagi kutish vaqti, uy narxi, kasallik darajasi qo'shni mahallanikiga yaqin. Bu model uchun foydali signal, lekin baholash uchun tuzoq: tasodifiy kross-validatsiyada test nuqtasining "qo'shnisi" o'quv to'plamida qoladi va model yangi hudud uchun qanchalik yomon ekanini yashiradi (17.8-darsdagi sizishning fazoviy ko'rinishi).
Real vaziyat. Taksi xizmati mijoz kutish vaqtini bashorat qilish modelini qurdi va tasodifiy 5-foldli CV da ajoyib natija oldi. Model Toshkentning yangi tumanlariga xizmat kengaygan paytda ishga tushirildi — va u yerda xato kutilganidan sezilarli katta chiqdi. Sabab: CV da har test safarining yonida, bir necha yuz metr narida o'quv safarlari bor edi, model esa "qo'shnilarni eslab" qolgan edi. 4-misolda aynan shu holatni o'lchaymiz: tasodifiy KFold yangi hudud xatosini muntazam kam ko'rsatadi, fazoviy blokli CV esa to'g'ri baholaydi.
Bu darsda geografik ma'lumot bilan ishlashning asosiy vositalarini noldan quramiz va har bir tuzoqni o'lchaymiz.
Bu darsda:
- Koordinatalar: kenglik, uzunlik, WGS84
- Masofa: gradusdagi Evklid xatosi, haversine formulasi, lokal proyeksiya
- Yo'nalish (azimut)
- Sintetik taksi safarlari Toshkent atrofida
- Fazoviy indekslash: panjara, geohash (noldan),
BallTree(metric="haversine") - Nuqta-poligon: ray casting noldan, fazoviy birlashtirish
- Fazoviy belgilar va maqsadli kodlashdagi sizish
- Fazoviy avtokorrelyatsiya (Moran I) va fazoviy kross-validatsiya
- Issiq nuqtalar: DBSCAN haversine bilan
- Tuzoqlar
ℹ Misollar numpy, pandas va sklearn bilan (Python 3.14).
geopandas,shapelyvafoliumo'rnatilmagan — ularning ishini kichik funksiyalar bilan noldan qilamiz, kutubxona kodi faqat nazariya bloklarida. Shaharlar koordinatalari taxminiy, tuman chegaralari va safarlar sintetik.
2. Nazariya — chuqur tushuntirish
2.1. Koordinatalar va WGS84
KENGLIK (latitude, lat): ekvatordan shimol (+) / janub (-), -90..+90 gradus
UZUNLIK (longitude, lon): Grinvichdan sharq (+) / g'arb (-), -180..+180 gradus
Toshkent (taxminan): lat 41.31, lon 69.28
WGS84 - GPS ishlatadigan standart (EPSG:4326): Yer - ellipsoid,
ekvator radiusi 6378.137 km, qutb radiusi 6356.752 km
ML uchun odatda SHAR yaqinlashuvi yetarli: R = 6371.0 km
(shahar va mamlakat miqyosida xato ~0.3% dan kam)
TARTIB TUZOG'I: (lat, lon) - odamlar va sklearn
(lon, lat) - GeoJSON, shapely, ko'p GIS formatlar (x, y)
ikkalasini aralashtirib yuborish - eng ko'p uchraydigan xato
ANIQLIK: 5 xona (0.00001 gradus) ~ 1.1 m; 3 xona ~ 110 m
GPS xatosi shaharda 5-20 m, binolar orasida - ko'proq2.2. Masofa: Evklid, haversine, lokal proyeksiya
GRADUSDAGI EVKLID (XATO):
d = sqrt(dlat^2 + dlon^2) * 111.2 km
muammo: 1 gradus uzunlik = 111.2 * cos(lat) km
Toshkentda cos(41.3) = 0.75 -> sharq-g'arb masofasi 1/0.75 = 1.33 marta oshadi
HAVERSINE (katta doira masofasi, shar ustida):
a = sin^2(dfi / 2) + cos(fi1) * cos(fi2) * sin^2(dlambda / 2)
d = 2 * R * arcsin( sqrt(a) )
fi - kenglik, lambda - uzunlik (RADIANDA!)
aniq (shar uchun), har qanday masofada
EKVIREKTANGULYAR (lokal proyeksiya):
x = R * dlambda * cos(fi0), y = R * dfi, d = sqrt(x^2 + y^2)
fi0 - hududning o'rtacha kengligi
shahar ichida xato ~0.01-0.2%; tekis koordinatalar -> Evklid,
KMeans, KNN, chiziqli modellar to'g'ridan-to'g'ri ishlaydi
AZIMUT (yo'nalish, 0 = shimol, 90 = sharq):
theta = atan2( sin(dlambda) cos(fi2),
cos(fi1) sin(fi2) - sin(fi1) cos(fi2) cos(dlambda) )"Qush uchishi" masofa yo'l masofasi emas. Ko'chalar egri, daryolar va temir yo'llar aylanib o'tishni talab qiladi: shaharda yo'l masofasi odatda to'g'ri chiziqdan 1.2-1.5 marta uzun. Haqiqiy yo'l masofasi va vaqti uchun marshrutlash xizmatlari (OSRM, Google Directions) yoki yo'l grafi kerak — lekin haversine ko'pincha kuchli boshlang'ich belgi.
Haqiqiy loyihada proyeksiyalar bilan pyproj/geopandas ishlaydi: masalan, O'zbekiston uchun UTM zonasi 42N (EPSG:32642) metr birligidagi tekis koordinatalar beradi.
import geopandas as gpd
gdf = gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df.lon, df.lat), crs="EPSG:4326")
gdf_m = gdf.to_crs("EPSG:32642") # UTM 42N, metrlarda
gdf_m["x"], gdf_m["y"] = gdf_m.geometry.x, gdf_m.geometry.y2.3. Fazoviy indekslash
"300 m ichidagi hamma nuqtalar" yoki "eng yaqin 5 ta haydovchi" — har so'rovda barcha N nuqtaga masofa hisoblash O(N). Million nuqta va minglab so'rovda bu juda sekin.
PANJARA (grid):
katak = (floor(lat / dlat), floor(lon / dlon)); lug'at: katak -> nuqtalar
radius r so'rovi: katak o'lchami >= r bo'lsa, 3 x 3 qo'shni katak yetarli
oddiy va tez; zichlik notekis bo'lsa - ba'zi kataklar to'lib ketadi
GEOHASH:
uzunlik va kenglik oraliqlarini navbat bilan ikkiga bo'lish (bit interleaving)
5 bit -> 1 belgi (base32): "tx3702"
UMUMIY PREFIKS = yaqinlik (odatda): "tx37..." - bitta ~20 km katak
uzunlik 5 ~ 4.9 x 3.7 km, 6 ~ 0.6 x 0.9 km, 7 ~ 150 x 110 m (Toshkentda)
TUZOQ: chegaraning ikki tomonidagi yaqin nuqtalar - boshqa kod
-> radius so'rovida qo'shni kataklar ham tekshiriladi
qo'llanishi: ma'lumotlar bazasida indeks (matn prefiksi), kalit sifatida
guruhlash, belgi (kategoriya)
BALLTREE (sklearn):
nuqtalarni ichma-ich "sharlar" ga ajratadi; so'rovda uzoq sharlar
butunlay tashlab yuboriladi
metric="haversine" - kirish RADIANDA [lat, lon], natija RADIANDA
radius = r_km / R, masofa_km = natija * R
kNN va radius so'rovlari; KDTree haversine ni qo'llamaydi
BOSHQALAR: H3 (Uber, olti burchakli kataklar), S2 (Google), R-tree (poligonlar)2.4. Nuqta-poligon va fazoviy birlashtirish
"Bu buyurtma qaysi tumanda?" — nuqta-poligon (point-in-polygon) savoli. Ko'p nuqtalarni poligonlar bilan bog'lash — fazoviy birlashtirish (spatial join), pd.merge ning fazoviy ko'rinishi.
RAY CASTING:
nuqtadan bir tomonga (masalan, sharqqa) nur chizamiz
nur poligon chegarasini TOQ marta kessa - nuqta ichkarida, JUFT - tashqarida
har qirra (y1,x1)-(y2,x2) uchun:
kesadi = (y1 > y) != (y2 > y) # qirra nur balandligini o'tadimi
x_k = x1 + (y - y1) * (x2 - x1) / (y2 - y1)
agar kesadi va x < x_k: ichida = not ichida
qavariq bo'lmagan poligonlarda ham ishlaydi; teshikli poligon -
tashqi va ichki halqalar alohida
TUZOQLAR: nuqta aynan chegarada (qaysi tumanga?); poligonlar bir-birini
yopib yoki bo'shliq qoldirib qo'ygan ("tashqarida" nuqtalar);
koordinata tartibi (lat, lon) va (x, y)import geopandas as gpd
tumanlar = gpd.read_file("tumanlar.geojson") # poligonlar
nuqtalar = gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df.lon, df.lat), crs="EPSG:4326")
birlashgan = gpd.sjoin(nuqtalar, tumanlar, how="left", predicate="within")2.5. Fazoviy belgilar
MASOFA BELGILARI: markazgacha, aeroportgacha, eng yaqin metro bekatigacha (km)
YO'NALISH: markazdan azimut -> sin va cos (aylana belgi: 359 va 1
gradus yaqin, lekin sonlar sifatida uzoq; 17.5-darsdagi
soat kabi)
KATAK BELGILARI: geohash / panjara katagi (kategoriya), katakdagi zichlik
AGREGATLAR: radius r ichidagi buyurtmalar soni, o'rtacha narx
(BallTree bilan)
MA'MURIY: tuman (nuqta-poligon orqali)
MAQSADLI KODLASH: katak bo'yicha maqsad o'rtachasi
⚠️ SIZISH: qatorning o'z maqsadi o'z belgisida -> kichik kataklarda
(1-3 qator) belgi = maqsad; CV haddan tashqari yaxshi
✅ out-of-fold: har fold uchun faqat boshqa foldlardan hisoblash,
silliqlash (umumiy o'rtachaga qarab tortish) - 17.8-darsDaraxt modellari (15-qism) lat va lon dan fazoviy tuzilmani o'zlari qisman o'rganadi — lekin faqat o'qlarga parallel bo'linishlar bilan. Chiziqli modellar esa lat va lon dan "markazgacha masofa" ni umuman o'rgana olmaydi — ularga bu belgilarni biz berishimiz kerak (3-misol).
2.6. Fazoviy avtokorrelyatsiya va Moran I
TOBLERNING 1-QONUNI: "hamma narsa hamma narsaga bog'liq,
lekin yaqin narsalar uzoqdagilarga qaraganda ko'proq"
MORAN I (global fazoviy avtokorrelyatsiya):
z_i = x_i - mean(x)
w_ij - fazoviy vaznlar (masalan: j - i ning k eng yaqin qo'shnisidan biri -> 1)
I = (N / S0) * sum_i sum_j w_ij z_i z_j / sum_i z_i^2, S0 = sum w_ij
I ~ -1/(N-1) ~ 0 tasodifiy joylashuv
I > 0 o'xshash qiymatlar yaqin (klaster)
I < 0 shaxmat taxtasi (qo'shnilar farqli)
p-qiymat: qiymatlarni joylar orasida tasodifiy aralashtirib (permutatsiya)
I ning "tasodifiy" taqsimoti bilan solishtirish (11-qism)
QOLDIQLARDA Moran I > 0 -> model fazoviy tuzilmani to'liq ushlamagan2.7. Fazoviy kross-validatsiya
TASODIFIY KFold: test nuqtasining qo'shnilari (100-300 m) o'quv to'plamida
-> model "qo'shnini eslab" bashorat qiladi
-> xato: "ma'lum hududdagi yangi nuqta" uchun TO'G'RI,
"yangi hudud" uchun OPTIMISTIK
FAZOVIY BLOKLI CV: hududni bloklarga bo'lamiz (masalan, 1.5 km),
GroupKFold(groups = blok) -> test bloki butunlay o'quvdan tashqarida
blok o'lchami >= avtokorrelyatsiya masofasi bo'lsin
(juda katta blok - pessimistik va kam fold)
QAYSI BIRI? SAVOLGA BOG'LIQ (18-qismdagi "baholash dizayni" qoidasi):
mavjud hududlarda yangi buyurtmalar -> tasodifiy (vaqt bo'yicha!) CV
yangi tuman / shahar / mamlakat -> blokli CV
vaqt ham bor bo'lsa -> vaqt + joy bo'yicha birgafrom sklearn.model_selection import GroupKFold, cross_val_score
blok = (np.floor(x_km / 1.5) * 1000 + np.floor(y_km / 1.5)).astype(int)
ball = cross_val_score(model, X, y, cv=GroupKFold(5), groups=blok,
scoring="neg_root_mean_squared_error")2.8. Issiq nuqtalar: DBSCAN haversine bilan
16.5-darsda DBSCAN ni ko'rgan edik: zichlik bo'yicha klasterlar, klasterlar sonini oldindan bermaymiz, shovqin nuqtalar alohida. Geografik ma'lumot uchun u juda qulay, chunki eps — metrdagi ma'noli radius:
DBSCAN(eps = 0.10 / R, min_samples = 50, metric="haversine",
algorithm="ball_tree").fit(np.radians(P))
eps: 100 m radius (radianda)
min_samples: shu radiusda kamida 50 ta nuqta -> "yadro"
natija: vokzal, bozor, aeroport kabi zich nuqtalar - klaster;
tarqoq buyurtmalar - shovqin (-1)
KMeans dan farqi: shakl va son erkin, shovqinni majburan klasterga
qo'shmaydi; Evklid emas, haversine
HDBSCAN (sklearn 1.3+): turli zichlikdagi klasterlar uchunXaritada ko'rish uchun amalda folium (Leaflet) ishlatiladi:
import folium
from folium.plugins import HeatMap
xarita = folium.Map(location=[41.31, 69.28], zoom_start=12)
HeatMap(df[["lat", "lon"]].values.tolist(), radius=8).add_to(xarita)
xarita.save("issiq_nuqtalar.html")2.9. Tuzoqlar
Asosiy tuzoqlar: gradus farqlaridan Evklid masofa; lat va lon tartibini aralashtirish; BallTree(haversine) ga gradus berish yoki natijani radiandan km ga o'tkazmaslik; "qush uchishi" ni yo'l masofasi deb olish; geohash qo'shnilarini tekshirmaslik; azimutni oddiy son sifatida berish (sin/cos o'rniga); kichik kataklarda maqsadli kodlashni o'z qatori bilan hisoblash; yangi hudud uchun model baholashda tasodifiy KFold; koordinatalarni chop etib, shaxsiy ma'lumotni (uy manzili) oshkor qilish; DBSCAN eps ni gradusda berish.
3. Tez ma'lumotnoma
import numpy as np
from sklearn.neighbors import BallTree
from sklearn.model_selection import GroupKFold
from sklearn.cluster import DBSCAN
R = 6371.0088 # km
def haversine(lat1, lon1, lat2, lon2):
f1, f2 = np.radians(lat1), np.radians(lat2)
a = (np.sin((f2 - f1) / 2) ** 2
+ np.cos(f1) * np.cos(f2) * np.sin(np.radians(lon2 - lon1) / 2) ** 2)
return 2 * R * np.arcsin(np.sqrt(a))
# lokal tekis koordinatalar (km)
x = np.radians(lon - lon0) * np.cos(np.radians(lat0)) * R
y = np.radians(lat - lat0) * R
# BallTree: RADIAN kiritish, RADIAN natija
daraxt = BallTree(np.radians(P), metric="haversine")
d, idx = daraxt.query(np.radians(Q), k=5) # d * R -> km
ichida = daraxt.query_radius(np.radians(Q), r=0.3 / R) # 300 m
# fazoviy blokli CV
blok = (np.floor(x / 1.5) * 1000 + np.floor(y / 1.5)).astype(int)
cv = GroupKFold(5) # .split(X, y, groups=blok)
# issiq nuqtalar
belgi = DBSCAN(eps=0.1 / R, min_samples=50, metric="haversine",
algorithm="ball_tree").fit_predict(np.radians(P))Qaysi vaziyatda nima
| Vazifa | Vosita |
|---|---|
| Ikki nuqta orasidagi masofa | haversine (istalgan masofa) |
| Shahar ichida tekis koordinata | ekvirektangulyar yoki UTM (pyproj) |
| Eng yaqin qo'shnilar, radius | BallTree(metric="haversine") |
| Ma'lumotlar bazasida joy bo'yicha qidiruv | geohash / H3 prefiks indeksi, PostGIS |
| Nuqta qaysi hududda | ray casting; geopandas.sjoin |
| Yo'nalish belgisi | azimut -> sin, cos |
| Katak bo'yicha maqsad | out-of-fold maqsadli kodlash |
| Yangi hudud uchun baholash | GroupKFold fazoviy bloklar bo'yicha |
| Issiq nuqtalar | DBSCAN (haversine, eps metrda) |
| Xarita | folium, geopandas.plot |
Geografik ma'lumot xulosasi
gradus - tekis emas: 1 gradus uzunlik = 111.2 * cos(lat) km
haversine - aniq; ekvirektangulyar - shaharda yetarli; yo'l != qush uchishi
indeks: panjara, geohash (+ qo'shnilar), BallTree (radian!)
belgilar: masofa, azimut (sin/cos), tuman, zichlik, TE (out-of-fold)
yaqin nuqtalar o'xshash (Moran I) -> yangi hudud uchun blokli CV4. Batafsil misollar
Misollar numpy, pandas va sklearn bilan (Python 3.14). Har misol mustaqil ishlaydi. Safarlar sintetik, Toshkent atrofida; koordinatalar taxminiy.
Misol 1 — Masofa: Evklid xatosi, haversine va lokal proyeksiya
"""Masofa: gradusdagi Evklid xatosi, haversine noldan, lokal proyeksiya va yo'nalish."""
import numpy as np
import pandas as pd
from sklearn.metrics.pairwise import haversine_distances
R_YER = 6371.0088 # Yerning o'rtacha radiusi, km
# Shaharlar markazlarining TAXMINIY koordinatalari (kenglik, uzunlik)
SHAHARLAR = {
"Toshkent": (41.311, 69.279),
"Samarqand": (39.655, 66.960),
"Buxoro": (39.768, 64.421),
"Namangan": (40.998, 71.672),
"Nukus": (42.460, 59.603),
"Chirchiq": (41.469, 69.582),
}
def haversine(lat1, lon1, lat2, lon2):
"""Katta doira masofasi (km). Argumentlar gradusda, numpy massiv bo'lishi mumkin."""
f1, f2 = np.radians(lat1), np.radians(lat2)
df, dl = f2 - f1, np.radians(lon2 - lon1)
a = np.sin(df / 2) ** 2 + np.cos(f1) * np.cos(f2) * np.sin(dl / 2) ** 2
return 2 * R_YER * np.arcsin(np.sqrt(np.clip(a, 0, 1)))
def evklid_gradus(lat1, lon1, lat2, lon2):
"""XATO usul: gradus farqlarini tekis koordinata deb olib, 111.2 ga ko'paytirish."""
return np.hypot(lat2 - lat1, lon2 - lon1) * 111.195
def ekvirektangulyar(lat1, lon1, lat2, lon2, lat0=None):
"""Lokal proyeksiya: x = R * dlon * cos(lat0), y = R * dlat (km)."""
if lat0 is None:
lat0 = (lat1 + lat2) / 2
x = np.radians(lon2 - lon1) * np.cos(np.radians(lat0)) * R_YER
y = np.radians(lat2 - lat1) * R_YER
return np.hypot(x, y)
def yonalish(lat1, lon1, lat2, lon2):
"""Boshlang'ich azimut (gradus, 0 = shimol, 90 = sharq)."""
f1, f2 = np.radians(lat1), np.radians(lat2)
dl = np.radians(lon2 - lon1)
x = np.sin(dl) * np.cos(f2)
y = np.cos(f1) * np.sin(f2) - np.sin(f1) * np.cos(f2) * np.cos(dl)
return (np.degrees(np.arctan2(x, y)) + 360) % 360
def main() -> None:
print("=== 1. Shaharlar orasidagi masofa (koordinatalar TAXMINIY) ===")
t = SHAHARLAR["Toshkent"]
print(f" {'Toshkent ->':<12} {'haversine':>10} {'sklearn':>9} {'Evklid(gr)':>11} "
f"{'xato %':>7} {'ekvirekt.':>10} {'xato %':>7} {'azimut':>7}")
for nom, (la, lo) in SHAHARLAR.items():
if nom == "Toshkent":
continue
h = haversine(t[0], t[1], la, lo)
sk = haversine_distances(np.radians([t]), np.radians([(la, lo)]))[0, 0] * R_YER
e = evklid_gradus(t[0], t[1], la, lo)
q = ekvirektangulyar(t[0], t[1], la, lo)
print(f" {nom:<12} {h:>10.1f} {sk:>9.1f} {e:>11.1f} {100 * (e - h) / h:>+7.1f} "
f"{q:>10.1f} {100 * (q - h) / h:>+7.2f} {yonalish(t[0], t[1], la, lo):>7.0f}")
print(" (yo'l masofasi to'g'ri chiziqdan uzunroq - bu yerda 'qush uchishi')")
print("\n=== 2. 1 gradus necha km? (kenglikka bog'liq) ===")
for lat in [0, 30, 41.3, 60]:
print(f" kenglik {lat:>5}: 1 gradus kenglik = {haversine(lat, 69, lat + 1, 69):6.1f} km, "
f"1 gradus uzunlik = {haversine(lat, 69, lat, 70):6.1f} km")
print("\n=== 3. Sintetik taksi safarlari (Toshkent atrofi) ===")
rng = np.random.default_rng(0)
markazlar = np.array([[41.326, 69.228], # Chorsu atrofi (sintetik)
[41.311, 69.279], # markaz
[41.258, 69.281], # aeroport tomoni
[41.365, 69.290], # shimol (turar joy)
[41.285, 69.200], # g'arb (turar joy)
[41.230, 69.220]]) # janub
ulush = np.array([0.22, 0.28, 0.10, 0.15, 0.15, 0.10])
n = 20_000
k = rng.choice(len(markazlar), n, p=ulush)
olish = markazlar[k] + rng.normal(0, 0.012, (n, 2))
km = rng.lognormal(np.log(5), 0.6, n) # to'g'ri chiziq masofasi
burchak = rng.uniform(0, 2 * np.pi, n)
dlat = km * np.cos(burchak) / 111.195
dlon = km * np.sin(burchak) / (111.195 * np.cos(np.radians(olish[:, 0])))
tushish = olish + np.c_[dlat, dlon]
soat = rng.choice(24, n, p=np.r_[np.full(6, 0.01), np.full(4, 0.07),
np.full(6, 0.04), np.full(4, 0.07), np.full(4, 0.035)])
tirband = np.isin(soat, [7, 8, 9, 17, 18, 19])
h = haversine(olish[:, 0], olish[:, 1], tushish[:, 0], tushish[:, 1])
yol = h * rng.uniform(1.2, 1.45, n) # ko'chalar egri
narx = (6000 + 2200 * yol) * np.where(tirband, 1.3, 1.0) * rng.lognormal(0, 0.08, n)
df = pd.DataFrame({"olish_lat": olish[:, 0], "olish_lon": olish[:, 1],
"tushish_lat": tushish[:, 0], "tushish_lon": tushish[:, 1],
"soat": soat, "narx": narx.round(-2)})
print(f" safarlar: {len(df)}, kenglik {df.olish_lat.min():.3f}-{df.olish_lat.max():.3f}, "
f"uzunlik {df.olish_lon.min():.3f}-{df.olish_lon.max():.3f}")
print(f" haversine: mediana {np.median(h):.2f} km, 95-persentil {np.quantile(h, 0.95):.2f} km")
print(f" narx: mediana {df.narx.median():.0f} so'm, tirbandlikda "
f"{df.narx[tirband].median() / df.narx[~tirband].median():.2f} barobar")
print("\n=== 4. Shahar ichida qaysi masofa yetarli? ===")
e = evklid_gradus(olish[:, 0], olish[:, 1], tushish[:, 0], tushish[:, 1])
q = ekvirektangulyar(olish[:, 0], olish[:, 1], tushish[:, 0], tushish[:, 1], lat0=41.3)
for nom, x in [("Evklid (gradus)", e), ("ekvirektangulyar", q)]:
nisbiy = (x - h) / h
print(f" {nom:<17} o'rtacha xato {100 * nisbiy.mean():+6.2f}%, "
f"maks |xato| {100 * np.abs(nisbiy).max():6.2f}%")
for nom, x in [("haversine", h), ("Evklid (gradus)", e)]:
r = np.corrcoef(x, df.narx)[0, 1]
b = np.polyfit(x, df.narx, 1)[0]
print(f" narx ~ {nom:<16} korrelyatsiya {r:.4f}, 1 km uchun {b:7.0f} so'm")
print(" ⭐ Evklid(gradus) sharq-g'arb masofasini ~1/cos(41.3) = "
f"{1 / np.cos(np.radians(41.3)):.2f} marta oshiradi")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Shaharlar orasidagi masofa (koordinatalar TAXMINIY) ===
Toshkent -> haversine sklearn Evklid(gr) xato % ekvirekt. xato % azimut
Samarqand 269.0 269.0 316.9 +17.8 269.0 +0.01 228
Buxoro 444.8 444.8 566.8 +27.4 444.9 +0.02 249
Namangan 203.3 203.3 268.4 +32.0 203.3 +0.00 99
Nukus 810.7 810.7 1083.5 +33.7 811.1 +0.06 282
Chirchiq 30.8 30.8 38.0 +23.4 30.8 +0.00 55
(yo'l masofasi to'g'ri chiziqdan uzunroq - bu yerda 'qush uchishi')
=== 2. 1 gradus necha km? (kenglikka bog'liq) ===
kenglik 0: 1 gradus kenglik = 111.2 km, 1 gradus uzunlik = 111.2 km
kenglik 30: 1 gradus kenglik = 111.2 km, 1 gradus uzunlik = 96.3 km
kenglik 41.3: 1 gradus kenglik = 111.2 km, 1 gradus uzunlik = 83.5 km
kenglik 60: 1 gradus kenglik = 111.2 km, 1 gradus uzunlik = 55.6 km
=== 3. Sintetik taksi safarlari (Toshkent atrofi) ===
safarlar: 20000, kenglik 41.191-41.402, uzunlik 69.155-69.329
haversine: mediana 4.98 km, 95-persentil 13.37 km
narx: mediana 23000 so'm, tirbandlikda 1.30 barobar
=== 4. Shahar ichida qaysi masofa yetarli? ===
Evklid (gradus) o'rtacha xato +17.02%, maks |xato| 33.29%
ekvirektangulyar o'rtacha xato +0.00%, maks |xato| 0.17%
narx ~ haversine korrelyatsiya 0.9395, 1 km uchun 3308 so'm
narx ~ Evklid (gradus) korrelyatsiya 0.9239, 1 km uchun 2731 so'm
⭐ Evklid(gradus) sharq-g'arb masofasini ~1/cos(41.3) = 1.33 marta oshiradiNatija tahlili.
1-bo'lim — shaharlar orasidagi masofa (koordinatalar taxminiy, shuning uchun raqamlar ham taxminiy — masalan, Toshkent-Samarqand to'g'ri chiziqda ~269 km, yo'l bo'ylab esa ~300 km dan ortiq). Noldan yozilgan haversine sklearn.metrics.pairwise.haversine_distances bilan aynan mos. Gradus farqlaridan Evklid masofa har doim oshiradi: +17.8% (Samarqand, janubi-g'arb) dan +33.7% gacha (Nukus, deyarli g'arb). Xato yo'nalishga bog'liq: sharq-g'arb yo'nalishida (Namangan — azimut 99, Nukus — 282) eng katta, chunki aynan uzunlik gradusi "qisqaroq". Ekvirektangulyar proyeksiya esa hatto 800 km da ham 0.06% xato beradi.
2-bo'lim — sababi: bir gradus kenglik hamma joyda 111.2 km, bir gradus uzunlik esa ekvatorda 111.2, Toshkent kengligida 83.5, 60-kenglikda 55.6 km.
3-bo'lim — sintetik safarlar: 20 000 ta, olish nuqtalari oltita markaz atrofida (markaz, Chorsu tomoni, aeroport tomoni, turar joy massivlari). To'g'ri chiziq masofasi mediana 4.98 km, 95-persentil 13.37 km. Narx = boshlang'ich to'lov + yo'l km (to'g'ri chiziqdan 1.2-1.45 marta uzun) + tirbandlik koeffitsienti: mediana 23000 so'm, tirband soatlarda 1.30 barobar.
4-bo'lim — shahar ichida: gradusdagi Evklid o'rtacha +17.02%, eng ko'pi 33.29% xato beradi; ekvirektangulyar — 0.00% o'rtacha, 0.17% eng ko'pi. Narx bilan korrelyatsiya ham haversine uchun yuqoriroq (0.9395 va 0.9239) va "1 km narxi" to'g'ri chiqadi: haversine bo'yicha 3308 so'm (yo'l koeffitsienti bilan 2200 * ~1.33 * tirbandlik ga mos), Evklid bo'yicha esa noto'g'ri 2731 — chunki "kilometr" o'zi noto'g'ri o'lchangan. Ya'ni xato faqat masofada qolmaydi: undan hisoblangan har bir belgi va koeffitsient buziladi.
Misol 2 — Fazoviy indekslash: panjara, geohash va BallTree
"""Fazoviy indekslash: panjara, geohash noldan va BallTree(haversine)."""
import numpy as np
from sklearn.neighbors import BallTree
R_YER = 6371.0088
BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz"
def haversine(lat1, lon1, lat2, lon2):
f1, f2 = np.radians(lat1), np.radians(lat2)
a = (np.sin((f2 - f1) / 2) ** 2
+ np.cos(f1) * np.cos(f2) * np.sin(np.radians(lon2 - lon1) / 2) ** 2)
return 2 * R_YER * np.arcsin(np.sqrt(np.clip(a, 0, 1)))
def olish_nuqtalari(n, urug=0):
"""1-misoldagi sintetik taksi olish nuqtalari (Toshkent atrofi)."""
rng = np.random.default_rng(urug)
markazlar = np.array([[41.326, 69.228], [41.311, 69.279], [41.258, 69.281],
[41.365, 69.290], [41.285, 69.200], [41.230, 69.220]])
k = rng.choice(6, n, p=[0.22, 0.28, 0.10, 0.15, 0.15, 0.10])
return markazlar[k] + rng.normal(0, 0.012, (n, 2))
def geohash_kodla(lat, lon, uzunlik=7):
"""Kenglik va uzunlik oraliqlarini navbat bilan ikkiga bo'lish (bit
interleaving): juft bit - uzunlik, toq bit - kenglik; 5 bit = 1 belgi."""
lat_or, lon_or = [-90.0, 90.0], [-180.0, 180.0]
kod, bit, qiymat, juft = [], 0, 0, True
while len(kod) < uzunlik:
oraliq, x = (lon_or, lon) if juft else (lat_or, lat)
orta = (oraliq[0] + oraliq[1]) / 2
if x >= orta:
qiymat = qiymat * 2 + 1
oraliq[0] = orta
else:
qiymat = qiymat * 2
oraliq[1] = orta
juft = not juft
bit += 1
if bit == 5:
kod.append(BASE32[qiymat])
bit, qiymat = 0, 0
return "".join(kod)
def geohash_dekodla(kod):
"""Katak markazi va yarim o'lchamlari (gradus)."""
lat_or, lon_or = [-90.0, 90.0], [-180.0, 180.0]
juft = True
for belgi in kod:
q = BASE32.index(belgi)
for i in range(4, -1, -1):
oraliq = lon_or if juft else lat_or
orta = (oraliq[0] + oraliq[1]) / 2
if (q >> i) & 1:
oraliq[0] = orta
else:
oraliq[1] = orta
juft = not juft
return ((lat_or[0] + lat_or[1]) / 2, (lon_or[0] + lon_or[1]) / 2,
(lat_or[1] - lat_or[0]) / 2, (lon_or[1] - lon_or[0]) / 2)
def geohash_qoshnilar(kod):
"""8 ta qo'shni katak: markazni bir katakka siljitib qayta kodlash."""
lat, lon, dlat, dlon = geohash_dekodla(kod)
return sorted({geohash_kodla(lat + i * 2 * dlat, lon + j * 2 * dlon, len(kod))
for i in (-1, 0, 1) for j in (-1, 0, 1)} - {kod})
class Panjara:
"""Oddiy panjara indeksi: katak (i, j) -> nuqtalar ro'yxati."""
def __init__(self, nuqtalar, katak_km, lat0=41.3):
self.dlat = katak_km / 111.195
self.dlon = katak_km / (111.195 * np.cos(np.radians(lat0)))
self.nuqtalar = nuqtalar
ij = self.katak(nuqtalar[:, 0], nuqtalar[:, 1])
self.jadval = {}
for n, (i, j) in enumerate(ij.tolist()):
self.jadval.setdefault((i, j), []).append(n)
def katak(self, lat, lon):
return np.c_[np.floor(lat / self.dlat), np.floor(lon / self.dlon)].astype(int)
def radius(self, lat, lon, r_km):
"""Katak o'lchami >= r bo'lsa, 3 x 3 qo'shni kataklar yetarli."""
i, j = self.katak(np.array([lat]), np.array([lon]))[0]
nomzod = [n for di in (-1, 0, 1) for dj in (-1, 0, 1)
for n in self.jadval.get((i + di, j + dj), [])]
nomzod = np.array(nomzod, dtype=int)
d = haversine(lat, lon, self.nuqtalar[nomzod, 0], self.nuqtalar[nomzod, 1])
return np.sort(nomzod[d <= r_km]), len(nomzod)
def main() -> None:
P = olish_nuqtalari(20_000)
rng = np.random.default_rng(1)
so_rov = P[rng.choice(len(P), 200, replace=False)] + rng.normal(0, 0.002, (200, 2))
print("=== 1. Geohash noldan ===")
lat, lon = 41.311, 69.279 # Toshkent markazi (taxminiy)
for u in [4, 5, 6, 7]:
kod = geohash_kodla(lat, lon, u)
la, lo, dla, dlo = geohash_dekodla(kod)
print(f" uzunlik {u}: {kod:<8} katak ~ {2 * dla * 111.195:6.2f} km x "
f"{2 * dlo * 111.195 * np.cos(np.radians(lat)):6.2f} km, "
f"markaz xatosi {1000 * haversine(lat, lon, la, lo):7.1f} m")
kod6 = geohash_kodla(lat, lon, 6)
print(f" {kod6} ning qo'shnilari: {geohash_qoshnilar(kod6)}")
la6, lo6, _, dlo6 = geohash_dekodla(kod6)
a, b = (la6, lo6 - dlo6 - 0.00005), (la6, lo6 - dlo6 + 0.00005) # chegara atrofi
print(f" ~{1000 * haversine(*a, *b):.0f} m oraliqdagi ikki nuqta: "
f"{geohash_kodla(*a, 6)} va {geohash_kodla(*b, 6)}")
print(" (chegaraning ikki tomonida - kodlar boshqa; shuning uchun radius"
" so'rovida qo'shni kataklar ham tekshiriladi)")
print("\n=== 2. Radius so'rovi (300 m), 200 so'rov, 20 000 nuqta ===")
r_km = 0.3
to_liq, jami_brute = [], 0
for q in so_rov:
d = haversine(q[0], q[1], P[:, 0], P[:, 1])
to_liq.append(np.flatnonzero(d <= r_km))
jami_brute += len(P)
print(f" to'liq ko'rib chiqish: {jami_brute} ta masofa hisoblandi")
for katak in [0.3, 0.6, 1.2]:
panjara = Panjara(P, katak)
jami, mos = 0, True
for q, t in zip(so_rov, to_liq):
nat, nomzod = panjara.radius(q[0], q[1], r_km)
jami += nomzod
mos &= np.array_equal(nat, t)
print(f" panjara {katak:.1f} km: kataklar {len(panjara.jadval):>5}, "
f"masofalar {jami:>8} ({jami / jami_brute:.4f}), natija bir xil: {mos}")
daraxt = BallTree(np.radians(P), metric="haversine")
daraxt.reset_n_calls()
ind = daraxt.query_radius(np.radians(so_rov), r=r_km / R_YER)
mos = all(np.array_equal(np.sort(i), t) for i, t in zip(ind, to_liq))
print(f" BallTree(haversine): masofalar {daraxt.get_n_calls():>8} "
f"({daraxt.get_n_calls() / jami_brute:.4f}), natija bir xil: {mos}")
soni = np.array([len(t) for t in to_liq])
print(f" 300 m ichidagi nuqtalar: mediana {np.median(soni):.0f}, "
f"min {soni.min()}, maks {soni.max()}")
print("\n=== 3. Eng yaqin 5 qo'shni (BallTree) ===")
daraxt.reset_n_calls()
d, idx = daraxt.query(np.radians(so_rov), k=5)
d_km = d * R_YER
tekshir = np.sort(haversine(so_rov[0, 0], so_rov[0, 1], P[:, 0], P[:, 1]))[:5]
print(f" 1-so'rov: BallTree {np.round(1000 * d_km[0], 1).tolist()} m")
print(f" 1-so'rov: to'liq {np.round(1000 * tekshir, 1).tolist()} m")
print(f" 5-qo'shnigacha masofa: mediana {1000 * np.median(d_km[:, 4]):.0f} m, "
f"90-persentil {1000 * np.quantile(d_km[:, 4], 0.9):.0f} m")
print(f" masofa hisoblari: {daraxt.get_n_calls()} (to'liq: {jami_brute})")
print(" ⭐ BallTree radianda ishlaydi: kiritish np.radians, radius = km / R")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Geohash noldan ===
uzunlik 4: tx37 katak ~ 19.55 km x 29.36 km, markaz xatosi 16026.8 m
uzunlik 5: tx370 katak ~ 4.89 km x 3.67 km, markaz xatosi 2176.7 m
uzunlik 6: tx3702 katak ~ 0.61 km x 0.92 km, markaz xatosi 395.0 m
uzunlik 7: tx3702r katak ~ 0.15 km x 0.11 km, markaz xatosi 39.4 m
tx3702 ning qo'shnilari: ['tx36bp', 'tx36br', 'tx36bx', 'tx3700', 'tx3701', 'tx3703', 'tx3708', 'tx3709']
~8 m oraliqdagi ikki nuqta: tx3700 va tx3702
(chegaraning ikki tomonida - kodlar boshqa; shuning uchun radius so'rovida qo'shni kataklar ham tekshiriladi)
=== 2. Radius so'rovi (300 m), 200 so'rov, 20 000 nuqta ===
to'liq ko'rib chiqish: 4000000 ta masofa hisoblandi
panjara 0.3 km: kataklar 2030, masofalar 36685 0.0092-bob, natija bir xil: True
panjara 0.6 km: kataklar 626, masofalar 137241 0.0343-bob, natija bir xil: True
panjara 1.2 km: kataklar 189, masofalar 420768 0.1052-bob, natija bir xil: True
BallTree(haversine): masofalar 118218 0.0296-bob, natija bir xil: True
300 m ichidagi nuqtalar: mediana 58, min 2, maks 186
=== 3. Eng yaqin 5 qo'shni (BallTree) ===
1-so'rov: BallTree [58.4, 80.4, 83.5, 88.2, 99.7] m
1-so'rov: to'liq [58.4, 80.4, 83.5, 88.2, 99.7] m
5-qo'shnigacha masofa: mediana 88 m, 90-persentil 176 m
masofa hisoblari: 192901 (to'liq: 4000000)
⭐ BallTree radianda ishlaydi: kiritish np.radians, radius = km / RNatija tahlili.
1-bo'lim — geohash noldan. Toshkent markazi tx3702r (7 belgi). Har belgi katakni 32 marta kichraytiradi: 4 belgi — 819.55 x 29.36 km, 6 belgi — 0.61 x 0.92 km, 7 belgi — 0.15 x 0.11 km (oxirgi ustun — nuqtadan uning katagi markazigacha masofa: 7 belgida 39.4 m). Kataklar kvadrat emas: toq va juft uzunlikda kenglik va uzunlikka turlicha bit tushadi. tx3702 ning 8 qo'shnisidan uchtasi (tx36bp, tx36br, tx36bx) boshqa prefiks bilan boshlanadi — ular 5-belgi darajasida boshqa katakda. Chegaraning ikki tomonidagi ` m oraliqdagi ikki nuqta ham turli kod oldi (tx3700vatx3702`) — shuning uchun geohash bo'yicha qidiruvda qo'shni kataklar albatta qo'shiladi.
2-bo'lim — 200 ta radius so'rovi (300 m) 20 000 nuqtada. To'liq ko'rib chiqish — 4 000 000 masofa. Panjara 0.3 km kataklar bilan faqat 36685 ta masofa (0.92%) hisobladi — natija to'liq ko'rib chiqish bilan aynan bir xil. Katak kattalashgan sari nomzodlar ko'payadi: 1.2 km da 10.5%. BallTree 2.96% — panjaraning eng yaxshi sozlamasidan ko'proq, lekin u sozlashsiz ishlaydi va zichlik notekis bo'lsa ham barqaror. 300 m ichidagi nuqtalar soni juda farq qiladi: 2 dan 186 gacha — shahar zichligi notekis.
3-bo'lim — eng yaqin 5 qo'shni: BallTree va to'liq ko'rib chiqish aynan bir xil masofalarni berdi: [58.4, 80.4, 83.5, 88.2, 99.7] m. 5-qo'shnigacha masofa mediana 88 m — bu "mahalladagi talab" belgisi uchun tabiiy radius. kNN so'rovlari 192901 masofa hisobladi — to'liq ko'rib chiqishdan 20 marta kam.
Misol 3 — Nuqta-poligon, fazoviy belgilar va maqsadli kodlashdagi sizish
"""Nuqta-poligon (ray casting) noldan, fazoviy belgilar va maqsadli kodlashdagi sizish."""
import numpy as np
import pandas as pd
from matplotlib.path import Path
from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import KFold, train_test_split
R_YER = 6371.0088
MARKAZ = (41.311, 69.279)
# SINTETIK tuman chegaralari (haqiqiy ma'muriy chegaralar EMAS): (lat, lon) uchlar
TUMANLAR = {
"Markaz": [(41.290, 69.245), (41.335, 69.245), (41.335, 69.300), (41.312, 69.315),
(41.290, 69.300)],
"Shimol": [(41.335, 69.200), (41.420, 69.200), (41.420, 69.340), (41.335, 69.340),
(41.335, 69.300), (41.335, 69.245)],
"G'arb": [(41.230, 69.140), (41.335, 69.140), (41.335, 69.245), (41.290, 69.245),
(41.290, 69.230), (41.230, 69.230)],
"Janub": [(41.170, 69.230), (41.290, 69.230), (41.290, 69.300), (41.312, 69.315),
(41.260, 69.340), (41.170, 69.340)],
}
def haversine(lat1, lon1, lat2, lon2):
f1, f2 = np.radians(lat1), np.radians(lat2)
a = (np.sin((f2 - f1) / 2) ** 2
+ np.cos(f1) * np.cos(f2) * np.sin(np.radians(lon2 - lon1) / 2) ** 2)
return 2 * R_YER * np.arcsin(np.sqrt(np.clip(a, 0, 1)))
def ichidami(lat, lon, poligon):
"""Ray casting: nuqtadan sharqqa nur; chegarani toq marta kessa - ichkarida."""
ichida = np.zeros(len(lat), dtype=bool)
n = len(poligon)
for i in range(n):
(y1, x1), (y2, x2) = poligon[i], poligon[(i + 1) % n]
kesadi = (y1 > lat) != (y2 > lat) # qirra nur balandligini kesadimi
with np.errstate(divide="ignore", invalid="ignore"):
x_kesish = x1 + (lat - y1) * (x2 - x1) / (y2 - y1)
ichida ^= kesadi & (lon < x_kesish)
return ichida
def safarlar(n, urug=0):
"""Sintetik safarlar: olish nuqtasi, soat va mijozning KUTISH vaqti (daqiqa)."""
rng = np.random.default_rng(urug)
markazlar = np.array([[41.326, 69.228], [41.311, 69.279], [41.258, 69.281],
[41.365, 69.290], [41.285, 69.200], [41.230, 69.220]])
k = rng.choice(6, n, p=[0.22, 0.28, 0.10, 0.15, 0.15, 0.10])
P = markazlar[k] + rng.normal(0, 0.012, (n, 2))
soat = rng.integers(0, 24, n)
d_markaz = haversine(P[:, 0], P[:, 1], *MARKAZ)
# haydovchilar kam hududlar - silliq fazoviy maydon (sintetik)
tanqis = np.array([[41.37, 69.31], [41.24, 69.19], [41.30, 69.24]])
maydon = sum(3.0 * np.exp(-haversine(P[:, 0], P[:, 1], a, b) ** 2 / 2.0)
for a, b in tanqis)
tirband = np.isin(soat, [7, 8, 9, 17, 18, 19])
kutish = (3 + 0.35 * d_markaz + maydon + 2.5 * tirband
+ rng.gamma(2.0, 1.0, n))
return pd.DataFrame({"lat": P[:, 0], "lon": P[:, 1], "soat": soat,
"kutish": kutish})
def maqsadli_kodlash(katak_train, y_train, katak_boshqa, silliq=20):
"""Katak bo'yicha o'rtacha (umumiy o'rtachaga qarab silliqlangan)."""
umumiy = y_train.mean()
g = pd.DataFrame({"k": katak_train, "y": y_train}).groupby("k")["y"].agg(["sum", "count"])
qiymat = (g["sum"] + silliq * umumiy) / (g["count"] + silliq)
return pd.Series(katak_boshqa).map(qiymat).fillna(umumiy).to_numpy()
def main() -> None:
df = safarlar(20_000)
print("=== 1. Nuqta-poligon: ray casting noldan ===")
tuman = np.full(len(df), "tashqarida", dtype=object)
for nom, pol in TUMANLAR.items():
ich = ichidami(df.lat.to_numpy(), df.lon.to_numpy(), pol)
mpl = Path([(lo, la) for la, lo in pol]).contains_points(df[["lon", "lat"]].to_numpy())
tuman[ich] = nom
print(f" {nom:<7} nuqtalar {ich.sum():>6}, matplotlib bilan farq: "
f"{int((ich != mpl).sum())}")
df["tuman"] = tuman
print(" fazoviy birlashtirish (spatial join) - tuman bo'yicha:")
jad = df.groupby("tuman")["kutish"].agg(["count", "mean"]).sort_values("mean")
for nom, q in jad.iterrows():
print(f" {nom:<11} safarlar {int(q['count']):>6}, o'rtacha kutish {q['mean']:5.2f} daq")
print("\n=== 2. Fazoviy belgilar ===")
df["d_markaz"] = haversine(df.lat, df.lon, *MARKAZ)
f1, f2 = np.radians(MARKAZ[0]), np.radians(df.lat)
dl = np.radians(df.lon - MARKAZ[1])
burchak = np.arctan2(np.sin(dl) * np.cos(f2),
np.cos(f1) * np.sin(f2) - np.sin(f1) * np.cos(f2) * np.cos(dl))
df["sin_b"], df["cos_b"] = np.sin(burchak), np.cos(burchak) # burchak - aylana belgi
dlat, dlon = 0.3 / 111.195, 0.3 / (111.195 * np.cos(np.radians(41.3)))
df["katak"] = ((df.lat // dlat).astype(int).astype(str) + "_"
+ (df.lon // dlon).astype(int).astype(str))
df["zichlik"] = df.groupby("katak")["lat"].transform("size")
print(f" 300 m kataklar: {df.katak.nunique()} ta, katakdagi safarlar mediana "
f"{df.groupby('katak').size().median():.0f}, 1-3 safarli kataklar "
f"{(df.groupby('katak').size() <= 3).mean():.2f} ulush")
print(f" korrelyatsiya bilan kutish: d_markaz {np.corrcoef(df.d_markaz, df.kutish)[0, 1]:.3f}, "
f"zichlik {np.corrcoef(df.zichlik, df.kutish)[0, 1]:.3f}")
print("\n=== 3. Maqsadli kodlash (katak bo'yicha): sizish ===")
tr, te = train_test_split(df, test_size=0.3, random_state=0)
ytr, yte = tr.kutish.to_numpy(), te.kutish.to_numpy()
kf = KFold(5, shuffle=True, random_state=0)
te_sizgan = maqsadli_kodlash(tr.katak, ytr, tr.katak, silliq=0) # o'zi ham ichida!
te_oof = np.zeros(len(tr))
for a, b in kf.split(tr):
te_oof[b] = maqsadli_kodlash(tr.katak.iloc[a], ytr[a], tr.katak.iloc[b])
te_test = maqsadli_kodlash(tr.katak, ytr, te.katak)
asos = ["soat"]
toplamlar = {
"bazaviy (o'rtacha)": None,
"soat + lat/lon": asos + ["lat", "lon"],
"+ d_markaz, burchak, tuman": asos + ["lat", "lon", "d_markaz", "sin_b", "cos_b", "tuman_k"],
"+ TE sizgan (silliqsiz)": asos + ["lat", "lon", "te"],
"+ TE out-of-fold": asos + ["lat", "lon", "te"],
}
tr = tr.assign(tuman_k=tr.tuman.map({n: i for i, n in enumerate(sorted(df.tuman.unique()))}))
te = te.assign(tuman_k=te.tuman.map({n: i for i, n in enumerate(sorted(df.tuman.unique()))}))
print(f" {'belgilar':<28} {'CV RMSE':>8} {'test RMSE':>10} {'optimizm':>9}")
natija = {}
for nom, cols in toplamlar.items():
if cols is None:
cv = np.sqrt(np.mean((ytr - ytr.mean()) ** 2))
ts = np.sqrt(np.mean((yte - ytr.mean()) ** 2))
print(f" {nom:<28} {cv:>8.3f} {ts:>10.3f} {ts - cv:>+9.3f}")
continue
Xtr = tr.assign(te=te_sizgan if "sizgan" in nom else te_oof)[cols].to_numpy()
Xte = te.assign(te=te_test)[cols].to_numpy()
xato = []
for a, b in kf.split(Xtr):
m = HistGradientBoostingRegressor(max_iter=150, random_state=0).fit(Xtr[a], ytr[a])
xato.append(np.sqrt(np.mean((m.predict(Xtr[b]) - ytr[b]) ** 2)))
m = HistGradientBoostingRegressor(max_iter=150, random_state=0).fit(Xtr, ytr)
ts = np.sqrt(np.mean((m.predict(Xte) - yte) ** 2))
natija[nom] = (np.mean(xato), ts)
print(f" {nom:<28} {np.mean(xato):>8.3f} {ts:>10.3f} {ts - np.mean(xato):>+9.3f}")
print(" optimizm = test RMSE - CV RMSE (musbat - CV haddan tashqari yaxshi ko'rsatgan)")
s, o = natija["+ TE sizgan (silliqsiz)"], natija["+ TE out-of-fold"]
if s[0] < o[0] and s[1] > o[1]:
print(" ⭐ sizgan TE: CV da eng yaxshi, testda yomonroq - maqsad o'zidan 'ko'rindi'")
print("\n=== 4. Chiziqli model uchun fazoviy belgilar (test RMSE) ===")
for d in (tr, te):
d["tirband"] = d.soat.isin([7, 8, 9, 17, 18, 19]).astype(float)
for nom, cols in [("tirband + lat/lon", ["tirband", "lat", "lon"]),
("+ d_markaz", ["tirband", "lat", "lon", "d_markaz"]),
("+ d_markaz, burchak", ["tirband", "lat", "lon", "d_markaz",
"sin_b", "cos_b"])]:
m = LinearRegression().fit(tr[cols], ytr)
print(f" {nom:<22} {np.sqrt(np.mean((m.predict(te[cols]) - yte) ** 2)):.3f}")
print(" daraxt lat/lon dan o'zi 'masofa' ni o'rganadi; chiziqli model o'rgana olmaydi")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Nuqta-poligon: ray casting noldan ===
Markaz nuqtalar 5511, matplotlib bilan farq: 0
Shimol nuqtalar 4054, matplotlib bilan farq: 0
G'arb nuqtalar 6972, matplotlib bilan farq: 0
Janub nuqtalar 2625, matplotlib bilan farq: 0
fazoviy birlashtirish (spatial join) - tuman bo'yicha:
Markaz safarlar 5511, o'rtacha kutish 6.19 daq
Janub safarlar 2625, o'rtacha kutish 7.82 daq
G'arb safarlar 6972, o'rtacha kutish 8.07 daq
Shimol safarlar 4054, o'rtacha kutish 8.15 daq
tashqarida safarlar 838, o'rtacha kutish 9.60 daq
=== 2. Fazoviy belgilar ===
300 m kataklar: 2030 ta, katakdagi safarlar mediana 6, 1-3 safarli kataklar 0.37 ulush
korrelyatsiya bilan kutish: d_markaz 0.510, zichlik -0.283
=== 3. Maqsadli kodlash (katak bo'yicha): sizish ===
belgilar CV RMSE test RMSE optimizm
bazaviy (o'rtacha) 2.143 2.122 -0.021
soat + lat/lon 1.439 1.435 -0.004
+ d_markaz, burchak, tuman 1.439 1.433 -0.006
+ TE sizgan (silliqsiz) 1.373 1.564 +0.190
+ TE out-of-fold 1.439 1.438 -0.001
optimizm = test RMSE - CV RMSE (musbat - CV haddan tashqari yaxshi ko'rsatgan)
⭐ sizgan TE: CV da eng yaxshi, testda yomonroq - maqsad o'zidan 'ko'rindi'
=== 4. Chiziqli model uchun fazoviy belgilar (test RMSE) ===
tirband + lat/lon 1.753
+ d_markaz 1.474
+ d_markaz, burchak 1.470
daraxt lat/lon dan o'zi 'masofa' ni o'rganadi; chiziqli model o'rgana olmaydiNatija tahlili.
1-bo'lim — ray casting noldan to'rtta sintetik tuman poligonida (ikkitasi qavariq emas) matplotlib.path.Path.contains_points bilan nol farq berdi. Fazoviy birlashtirish natijasi: Markaz tumanida o'rtacha kutish eng kam (6.19 daqiqa), Shimol va G'arbda 8.07-8.15, poligonlardan tashqaridagi 838 safarda — 9.60. "Tashqarida" qatori muhim: haqiqiy chegaralarda ham bo'shliqlar va ustma-ust tushishlar bo'ladi — ularni alohida sanang, yo'qotib qo'ymang.
2-bo'lim — fazoviy belgilar. Markazgacha masofa kutish bilan kuchli bog'liq (0.510), zichlik — manfiy (-0.283: gavjum joyda haydovchi ko'p). 300 m kataklar 2030 ta, lekin katakdagi safarlar mediana 6 va kataklarning 37% ida 1-3 ta safar — maqsadli kodlash uchun xavfli sharoit.
3-bo'lim — HistGB bilan belgilar to'plamlari (5-foldli CV va alohida test):
- Bazaviy (o'rtacha)
2.122→soat + lat/lon1.435. Daraxt koordinatalardan fazoviy tuzilmani o'zi o'rgandi. d_markaz, burchak (sin/cos) va tuman qo'shilishi daraxtga deyarli hech narsa bermadi (1.433) — 14 000 qatorda lat/lon yetarli. Bu halol natija: muhandislik qilingan belgilar har doim ham yordam bermaydi.- Sizgan TE (katak o'rtachasi qatorning o'zi bilan, silliqlashsiz): CV da eng yaxshi (
1.373), testda esa eng yomon modellardan (1.564) — optimizm+0.190. Kichik katakda qatorning o'z maqsadi uning belgisiga kirgan, model uni "ko'rgan". - Out-of-fold TE (silliqlash bilan): CV
1.439, test1.438— optimizm-0.001, halol baho. Foyda bermadi, lekin zarar ham yo'q.
4-bo'lim — chiziqli model uchun esa fazoviy belgilar hal qiluvchi: tirband + lat/lon bilan test RMSE 1.753, d_markaz qo'shilganda 1.474 — chunki "markazdan uzoqlashgan sari kutish oshadi" bog'liqligi lat va lon ga chiziqli emas. Burchak qo'shimcha deyarli bermadi (1.470).
Misol 4 — Fazoviy avtokorrelyatsiya, fazoviy CV va DBSCAN issiq nuqtalar
"""Fazoviy avtokorrelyatsiya (Moran I), fazoviy CV va DBSCAN(haversine) issiq nuqtalar."""
import numpy as np
from sklearn.cluster import DBSCAN
from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.model_selection import GroupKFold, KFold
from sklearn.neighbors import BallTree, KNeighborsRegressor
R_YER = 6371.0088
def km_xy(lat, lon, lat0=41.3, lon0=69.25):
"""Lokal ekvirektangulyar proyeksiya (km) - shahar miqyosida yetarli."""
x = np.radians(lon - lon0) * np.cos(np.radians(lat0)) * R_YER
y = np.radians(lat - lat0) * R_YER
return np.c_[x, y]
def malumot(n, urug=0):
"""Olish nuqtalari va kutish vaqti: silliq, lekin NOMA'LUM fazoviy maydon."""
rng = np.random.default_rng(urug)
markazlar = np.array([[41.326, 69.228], [41.311, 69.279], [41.258, 69.281],
[41.365, 69.290], [41.285, 69.200], [41.230, 69.220]])
k = rng.choice(6, n, p=[0.22, 0.28, 0.10, 0.15, 0.15, 0.10])
P = markazlar[k] + rng.normal(0, 0.012, (n, 2))
# issiq nuqtalar (sintetik, taxminiy joylar): vokzal, bozor, aeroport
issiq = np.array([[41.2925, 69.2870], [41.3265, 69.2355], [41.2600, 69.2810]])
h = rng.random(n) < 0.12
P[h] = issiq[rng.integers(0, 3, h.sum())] + rng.normal(0, 0.0006, (h.sum(), 2))
xy = km_xy(P[:, 0], P[:, 1])
maydon_rng = np.random.default_rng(123) # maydon har doim bir xil
c = maydon_rng.uniform(xy.min(0) - 1, xy.max(0) + 1, (80, 2))
amp = maydon_rng.normal(0, 1.5, 80)
maydon = sum(a * np.exp(-((xy - ci) ** 2).sum(1) / (2 * 0.8 ** 2))
for a, ci in zip(amp, c))
soat = rng.integers(0, 24, n)
tirband = np.isin(soat, [7, 8, 9, 17, 18, 19]).astype(float)
y = 6 + maydon + 2 * tirband + rng.normal(0, 1.0, n)
return P, xy, np.c_[xy, tirband], y
def moran_i(qiymat, xy, k=10, permutatsiya=99, urug=0):
"""Moran I: k eng yaqin qo'shni (teng vazn). ~0 - tasodifiy, >0 - o'xshashlar yaqin."""
z = qiymat - qiymat.mean()
_, idx = BallTree(xy).query(xy, k=k + 1)
qoshni = idx[:, 1:] # o'zini tashlaymiz
def hisobla(zz):
return len(zz) * np.sum(zz[:, None] * zz[qoshni]) / (qoshni.size * np.sum(zz ** 2))
I = hisobla(z)
rng = np.random.default_rng(urug)
tasodif = np.array([hisobla(rng.permutation(z)) for _ in range(permutatsiya)])
p = (1 + np.sum(tasodif >= I)) / (permutatsiya + 1)
return I, tasodif.mean(), p
def rmse(a, b):
return float(np.sqrt(np.mean((a - b) ** 2)))
def cv_rmse(model_fn, X, y, bolinish, groups=None):
xato = []
for a, b in bolinish.split(X, y, groups):
m = model_fn().fit(X[a], y[a])
xato.append(rmse(m.predict(X[b]), y[b]))
return float(np.mean(xato))
def main() -> None:
P, xy, X, y = malumot(12_000)
print("=== 1. Fazoviy avtokorrelyatsiya: Moran I (k = 10, 3000 nuqta) ===")
tanlov = np.random.default_rng(1).choice(len(y), 3000, replace=False)
for nom, q in [("kutish vaqti", y[tanlov]),
("aralashtirilgan kutish", np.random.default_rng(2).permutation(y[tanlov]))]:
I, kutilgan, p = moran_i(q, xy[tanlov])
print(f" {nom:<24} I = {I:6.3f} (tasodifiy ~ {kutilgan:+.3f}), p = {p:.2f}")
print("\n=== 2. Tasodifiy KFold va fazoviy blokli CV ===")
blok = 1.5 # km
guruh = (np.floor(xy[:, 0] / blok) * 1000 + np.floor(xy[:, 1] / blok)).astype(int)
print(f" {blok} km bloklar: {len(np.unique(guruh))} ta")
# "haqiqiy" savol: xizmat YANGI hududlarga chiqadi - bloklarning 20% i butunlay yangi
bloklar = np.unique(guruh)
yangi = np.random.default_rng(3).choice(bloklar, len(bloklar) // 5, replace=False)
test = np.isin(guruh, yangi)
Xtr, ytr, gtr = X[~test], y[~test], guruh[~test]
print(f" o'quv: {len(ytr)} safar, yangi hudud (test): {int(test.sum())} safar")
modellar = {
"KNN (k=10)": lambda: KNeighborsRegressor(10),
"HistGB": lambda: HistGradientBoostingRegressor(max_iter=150, random_state=0),
}
print(f" {'model':<11} {'tasodifiy KFold':>16} {'blokli CV':>10} "
f"{'yangi hudud':>12} {'bazaviy':>8}")
natija = {}
for nom, fn in modellar.items():
r_kf = cv_rmse(fn, Xtr, ytr, KFold(5, shuffle=True, random_state=0))
r_bl = cv_rmse(fn, Xtr, ytr, GroupKFold(5), gtr)
r_te = rmse(fn().fit(Xtr, ytr).predict(X[test]), y[test])
natija[nom] = (r_kf, r_bl, r_te)
print(f" {nom:<11} {r_kf:>16.3f} {r_bl:>10.3f} {r_te:>12.3f} "
f"{rmse(np.full(test.sum(), ytr.mean()), y[test]):>8.3f}")
print(" yangi hudud - o'quvda umuman bo'lmagan bloklardagi HAQIQIY xato")
for nom, (r_kf, r_bl, r_te) in natija.items():
print(f" {nom:<11} optimizm: tasodifiy KFold {r_te - r_kf:+.3f}, "
f"blokli {r_te - r_bl:+.3f}")
print(" KNN, 8 xil 'yangi hudud' tanlovi - optimizm (yangi hudud - CV):")
opt_kf, opt_bl = [], []
for s in range(8):
yangi = np.random.default_rng(100 + s).choice(bloklar, len(bloklar) // 5,
replace=False)
tt = np.isin(guruh, yangi)
fn = modellar["KNN (k=10)"]
r_te = rmse(fn().fit(X[~tt], y[~tt]).predict(X[tt]), y[tt])
opt_kf.append(r_te - cv_rmse(fn, X[~tt], y[~tt], KFold(5, shuffle=True, random_state=0)))
opt_bl.append(r_te - cv_rmse(fn, X[~tt], y[~tt], GroupKFold(5), guruh[~tt]))
for nom, o in [("tasodifiy KFold", opt_kf), ("blokli CV", opt_bl)]:
o = np.array(o)
print(f" {nom:<16} {o.mean():+.3f}, SE {o.std(ddof=1) / np.sqrt(len(o)):.3f}")
d = np.array(opt_kf) - np.array(opt_bl)
se = d.std(ddof=1) / np.sqrt(len(d))
print(f" farq (juftlashgan): {d.mean():+.3f}, SE {se:.3f}, "
f"sezilarli: {abs(d.mean()) > 2 * se}")
print("\n=== 3. Qoldiqlarda avtokorrelyatsiya (HistGB, tasodifiy KFold) ===")
q = np.zeros(len(ytr))
for a, b in KFold(5, shuffle=True, random_state=0).split(Xtr):
q[b] = ytr[b] - modellar["HistGB"]().fit(Xtr[a], ytr[a]).predict(Xtr[b])
t2 = np.random.default_rng(4).choice(len(q), 3000, replace=False)
I, kutilgan, p = moran_i(q[t2], Xtr[t2, :2])
print(f" qoldiqlar: I = {I:.3f} (tasodifiy ~ {kutilgan:+.3f}), p = {p:.2f}")
print("\n=== 4. Issiq nuqtalar: DBSCAN(haversine), eps = 100 m, min_samples = 50 ===")
db = DBSCAN(eps=0.10 / R_YER, min_samples=50, metric="haversine",
algorithm="ball_tree").fit(np.radians(P))
belgi = db.labels_
klaster = sorted(set(belgi) - {-1}, key=lambda c: -np.sum(belgi == c))
print(f" klasterlar: {len(klaster)}, shovqin nuqtalar ulushi {np.mean(belgi == -1):.3f}")
for i, c in enumerate(klaster[:6]):
m = P[belgi == c].mean(0)
print(f" #{i}: {np.sum(belgi == c):>5} nuqta, markaz ({m[0]:.4f}, {m[1]:.4f})")
if len(klaster) > 6:
print(f" (+{len(klaster) - 6} ta)")
print("\n=== 5. ASCII zichlik xaritasi (shimol tepada) ===")
qator, ustun = 16, 44
lat_ch = np.linspace(P[:, 0].min(), P[:, 0].max(), qator + 1)
lon_ch = np.linspace(P[:, 1].min(), P[:, 1].max(), ustun + 1)
H, _, _ = np.histogram2d(P[:, 0], P[:, 1], bins=[lat_ch, lon_ch])
belgilar = " -:=+*#%@"
darajalar = np.quantile(H[H > 0], np.linspace(0, 1, len(belgilar))[1:-1])
for r in range(qator - 1, -1, -1):
satr = "".join(" " if H[r, c] == 0 else belgilar[1 + np.searchsorted(darajalar, H[r, c])]
if np.searchsorted(darajalar, H[r, c]) < len(belgilar) - 1 else "@"
for c in range(ustun))
print(" |" + satr + "|")
print(f" kenglik {lat_ch[0]:.3f}-{lat_ch[-1]:.3f}, uzunlik {lon_ch[0]:.3f}-{lon_ch[-1]:.3f}")
print(" ⭐ yaqin nuqtalar o'xshash -> tasodifiy CV sizadi; yangi hudud uchun blokli CV")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Fazoviy avtokorrelyatsiya: Moran I (k = 10, 3000 nuqta) ===
kutish vaqti I = 0.330 (tasodifiy ~ -0.001), p = 0.01
aralashtirilgan kutish I = -0.009 (tasodifiy ~ -0.000), p = 0.84
=== 2. Tasodifiy KFold va fazoviy blokli CV ===
1.5 km bloklar: 116 ta
o'quv: 9587 safar, yangi hudud (test): 2413 safar
model tasodifiy KFold blokli CV yangi hudud bazaviy
KNN (k=10) 1.056 1.149 1.094 1.625
HistGB 1.040 1.151 1.075 1.625
yangi hudud - o'quvda umuman bo'lmagan bloklardagi HAQIQIY xato
KNN (k=10) optimizm: tasodifiy KFold +0.038, blokli -0.055
HistGB optimizm: tasodifiy KFold +0.035, blokli -0.076
KNN, 8 xil 'yangi hudud' tanlovi - optimizm (yangi hudud - CV):
tasodifiy KFold +0.076, SE 0.022
blokli CV -0.023, SE 0.027
farq (juftlashgan): +0.100, SE 0.007, sezilarli: True
=== 3. Qoldiqlarda avtokorrelyatsiya (HistGB, tasodifiy KFold) ===
qoldiqlar: I = -0.019 (tasodifiy ~ +0.001), p = 1.00
=== 4. Issiq nuqtalar: DBSCAN(haversine), eps = 100 m, min_samples = 50 ===
klasterlar: 3, shovqin nuqtalar ulushi 0.871
#0: 533 nuqta, markaz (41.3265, 69.2355)
#1: 520 nuqta, markaz (41.2600, 69.2810)
#2: 491 nuqta, markaz (41.2925, 69.2869)
=== 5. ASCII zichlik xaritasi (shimol tepada) ===
| ----: :=--::- - |
| - -=:++####***:::-: |
| - -:++*#%@@@@@%%#++::- |
| :::+::=-:: -::=##%%@@@%%%**==: |
| :-::++*#%#%#***===:+=+*####**++=: -|
| - -==*#%%@@@@@@%%**+**%%%%%#+*+==- : |
| --::+*%%@@@@@@@@%*#%@@@@@@@@@%#*+:- - |
|--::=++*#######%%%##**+*#%@@@@@@@@%%++:-- |
| =:++#%@@@@@%%#*+==: :==**#%%@@@@%#++--= |
|:-:**#@@@@@@%%#**=- --==:********+=-- |
|-:==++**####**++=: =:++##%%@%%%*+::: - |
| - -::=====+++==+:=-:==*##%%@%%#*++=:: |
| :--::***%%#%%#**+- :=:++##****=+=::- |
| -- -=+##%%%@@%#*=+- -=:-::=-:: |
| =:++#*###**++=- - - |
| : =:=+::-:--- |
kenglik 41.195-41.405, uzunlik 69.163-69.334
⭐ yaqin nuqtalar o'xshash -> tasodifiy CV sizadi; yangi hudud uchun blokli CVNatija tahlili.
Kutish vaqti bu yerda noma'lum silliq fazoviy maydondan (80 ta tasodifiy "tepalik", ~0.8 km o'lchamda), tirbandlik va shovqindan iborat — xuddi haqiqiy hayotdagidek, uning shaklini oldindan bilmaymiz.
1-bo'lim — Moran I (10 ta eng yaqin qo'shni, 3000 nuqta): kutish vaqti uchun I = 0.330, 99 ta permutatsiyada birortasi ham bunga yetmadi (p = 0.01 — 99 permutatsiyada mumkin bo'lgan eng kichik qiymat). Xuddi shu qiymatlarni joylar orasida aralashtirsak — I = -0.009, p = 0.84: tuzilma yo'qoladi. Demak yaqin safarlarning kutish vaqti haqiqatan o'xshash.
2-bo'lim — asosiy savol: xizmat yangi hududlarga chiqsa, model qanchalik yaxshi? 1.5 km bloklarning 20% i butunlay yangi hudud sifatida ajratiladi (2413 safar), qolganida (9587) model o'qitiladi va ikki xil CV qilinadi. Bitta tanlovda: KNN uchun tasodifiy KFold 1.056, blokli CV 1.149, haqiqiy yangi hudud xatosi 1.094. Bu tanlovda blokli CV biroz pessimistik chiqdi (-0.055), tasodifiy esa optimistik (+0.038) — bitta tanlov shovqinli, shuning uchun 8 xil "yangi hudud" tanlovida takrorlaymiz: tasodifiy KFold optimizmi o'rtacha +0.076 (SE 0.022), blokli CV — -0.023 (SE 0.027, noldan farqi sezilarli emas). Juftlashgan farq +0.100, SE 0.007 — aniq sezilarli. Tasodifiy KFold yangi hudud xatosini muntazam kam ko'rsatadi, blokli CV esa uni siljishsiz baholaydi. Bazaviy (o'rtacha) 1.625 — ikkala model ham undan ancha yaxshi, ya'ni yangi hududda ham fazoviy tuzilmaning bir qismi (qo'shni bloklardan) o'tadi.
3-bo'lim — HistGB qoldiqlarida Moran I -0.019 (p = 1.00) — model fazoviy tuzilmani ushlagan, qoldiqlarda avtokorrelyatsiya qolmagan. E'tibor bering: bu ham tasodifiy KFold qoldiqlari; qoldiq tozaligi baholash dizaynining to'g'riligini isbotlamaydi — 2-bo'lim ko'rsatganidek, optimizm baribir bor.
4-bo'lim — DBSCAN (haversine, eps = 100 m, min_samples = 50) uchta issiq nuqtani topdi — sintetik vokzal, bozor va aeroport (491-533 nuqta), markazlari ular qo'yilgan joylarga to'rtinchi xona aniqligida mos. Qolgan 87.1% safar — shovqin: ular zich emas, tarqoq talab. KMeans bu nuqtalarni ham majburan klasterlarga bo'lib yuborgan bo'lardi (16.5-dars).
5-bo'lim — ASCII zichlik xaritasi (shimol tepada, fon — bo'sh joy, zichlik - dan @ gacha kvantillar bo'yicha). Bir nechta zich markaz va ular orasidagi siyrak hududlar ko'rinadi. Issiq nuqtalar (70 m) bu masshtabda (500 m katak) alohida ko'rinmaydi — DBSCAN esa ularni aniq ajratdi: xarita va klasterlash bir-birini to'ldiradi.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
| "lat va lon — oddiy sonlar, Evklid yetarli" | Toshkentda sharq-g'arb masofasi 1.33 marta oshadi (1-misol: +17.8% dan +33.7% gacha) |
| "Haversine — yo'l masofasi" | Qush uchishi; shaharda yo'l 1.2-1.5 marta uzun |
| "Tekis proyeksiya har doim xato" | Shahar ichida ekvirektangulyar xatosi 0.17% dan kam |
| "Geohash prefiksi bir xil — nuqtalar yaqin, farqli — uzoq" | Chegara atrofida 8 m oraliqdagi nuqtalar turli kod oladi |
| "BallTree ga gradus beraman" | haversine — radianda; radius = km / R |
| "Muhandislik qilingan fazoviy belgilar har doim yordam beradi" | Daraxtga deyarli bermadi; chiziqli modelga — ko'p (3-misol) |
| "Katak bo'yicha o'rtacha — xavfsiz belgi" | O'z qatori bilan hisoblansa sizadi (optimizm +0.190) |
| "Tasodifiy KFold — standart, har doim to'g'ri" | Yangi hudud uchun optimistik (+0.076, 8 tanlov) |
| "Qoldiqlarda Moran I ~ 0 — baholash to'g'ri" | Qoldiq tozaligi CV dizaynini oqlamaydi |
6. Keng tarqalgan xatolar va yechimlari
1. Gradusdagi Evklid
d = np.hypot(lat2 - lat1, lon2 - lon1) * 111 # ⚠️
d = haversine(lat1, lon1, lat2, lon2) # ✅2. BallTree ga gradus
BallTree(P, metric="haversine").query_radius(Q, r=0.3) # ⚠️ gradus, km
BallTree(np.radians(P), metric="haversine").query_radius(np.radians(Q), r=0.3 / 6371) # ✅3. Koordinata tartibi
Point(lat, lon) # ⚠️ shapely (x, y)
Point(lon, lat) # ✅4. Azimut oddiy son sifatida
df["burchak"] = azimut # ⚠️ 359 va 1 - "uzoq"
df["sin_b"], df["cos_b"] = np.sin(np.radians(azimut)), np.cos(np.radians(azimut)) # ✅5. Sizgan maqsadli kodlash
df["te"] = df.groupby("katak")["y"].transform("mean") # ⚠️ o'zi ham ichida
# har fold uchun faqat o'quv qismidan, silliqlash bilan # ✅ out-of-fold6. Yangi hudud uchun tasodifiy CV
cross_val_score(model, X, y, cv=KFold(5, shuffle=True)) # ⚠️
cross_val_score(model, X, y, cv=GroupKFold(5), groups=blok) # ✅7. DBSCAN eps gradusda
DBSCAN(eps=0.001).fit(P) # ⚠️ gradus, Evklid
DBSCAN(eps=0.1 / 6371, metric="haversine").fit(np.radians(P)) # ✅ 100 m7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 16.5-dars (o'tilgan): DBSCAN — issiq nuqtalar haversine bilan
- 17.5, 17.8-darslar (o'tilgan): Aylana belgilar (sin/cos) va sizish — azimut va maqsadli kodlash
- 18-qism (o'tilgan): Baholash dizayni — fazoviy blokli CV
GroupKFoldbilan - 11-qism (o'tilgan): Permutatsiya testi — Moran I ning p-qiymati
- 28.10-dars: Katta ma'lumot bilan ishlash — millionlab safarlar; geohash kaliti bo'yicha guruhlash
- 28.11-dars: Sababiy xulosa — "yangi metro bekati atrofida talab oshdimi" kabi fazoviy savollar
- Amalda: taksi va yetkazib berish, ko'chmas mulk narxi, do'kon joyini tanlash, bank filiallari, epidemiologiya, qishloq xo'jaligi
8. Eng yaxshi amaliyotlar
Masofa uchun haversine yoki to'g'ri proyeksiya; gradusda Evklid hech qachon.
Koordinata tartibini (lat, lon yoki lon, lat) har funksiyada tekshiring; CRS ni yozib qo'ying.
Qidiruv uchun indeks: BallTree (radian), panjara yoki geohash + qo'shnilar.
Fazoviy belgilar: masofalar, azimut (sin/cos), tuman, zichlik; maqsadli kodlash faqat out-of-fold.
Moran I bilan avtokorrelyatsiyani o'lchang — maqsadda va qoldiqlarda.
Baholash savolga mos: yangi hudud — blokli CV; blok o'lchami >= avtokorrelyatsiya masofasi.
Issiq nuqtalar uchun DBSCAN, eps metrda o'ylab tanlang.
Joylashuv — shaxsiy ma'lumot: aniq koordinatalarni saqlashda va chop etishda ehtiyot bo'ling (yaxlitlash, agregatsiya).
9. Amaliy topshiriq
Vazifa 1: Bashorat qiling
1. # Toshkentda 1 gradus uzunlik necha km?
2. # gradusdagi Evklid qaysi yo'nalishda eng ko'p xato qiladi?
3. # ekvirektangulyar proyeksiya shahar ichida qancha xato beradi?
4. # BallTree(haversine) qanday birlikda ishlaydi?
5. # geohash uzunligi 6 - katak o'lchami?
6. # nega geohash qidiruvida qo'shni kataklar kerak?
7. # ray casting: nur chegarani 3 marta kesdi - nuqta qayerda?
8. # azimutni qanday belgi qilish kerak?
9. # sizgan TE da CV va test qanday bo'ladi?
10. # Moran I = 0.33 nimani bildiradi?
11. # yangi hudud uchun tasodifiy KFold?
12. # DBSCAN eps = 100 m ni qanday berasiz?Javoblar
111.2 * cos(41.3) ~ 83.5km- Sharq-g'arb (uzunlik bo'ylab):
+33.7%gacha 0.17%dan kam (1-misol)- Radian: kirish
np.radians, natija* Rkm - Toshkentda ~`0.61 x 0.92` km
- Chegara atrofidagi yaqin nuqtalar turli kod oladi
- Ichkarida (toq)
sinvacos- CV eng yaxshi (
1.373), test yomon (1.564) - Yaqin safarlarning kutish vaqti o'xshash (kuchli musbat avtokorrelyatsiya)
- Optimistik (
+0.076,SE 0.022) eps = 0.1 / 6371,metric="haversine", kirish radianda
Vazifa 2: Xatolarni tuzating
1. d_km = np.hypot(df.lat2 - df.lat1, df.lon2 - df.lon1) * 111
2. tree = BallTree(df[["lat", "lon"]].values, metric="haversine")
ind = tree.query_radius(q, r=0.5) # 500 m
3. df["yonalish"] = azimut(df.lat, df.lon, 41.31, 69.28)
4. df["katak_orta"] = df.groupby("katak")["narx"].transform("mean")
cross_val_score(model, df[belgilar], df.narx, cv=5)
5. cross_val_score(model, X, y, cv=KFold(5, shuffle=True)) # yangi shaharga chiqamizJavoblar
1. d_km = haversine(df.lat1, df.lon1, df.lat2, df.lon2)
2. tree = BallTree(np.radians(df[["lat", "lon"]].values), metric="haversine")
ind = tree.query_radius(np.radians(q), r=0.5 / 6371.0088)
3. a = np.radians(azimut(df.lat, df.lon, 41.31, 69.28))
df["sin_b"], df["cos_b"] = np.sin(a), np.cos(a)
4. # har fold ichida: o'quv qismidan silliqlangan o'rtacha, test qismiga map
# (yoki sklearn TargetEncoder - ichki cross-fitting bilan)
5. cross_val_score(model, X, y, cv=GroupKFold(5), groups=blok) # yoki shahar bo'yichaVazifa 3: Masofa
Modellang (1-misol asosida):
- Vincenty yoki ellipsoid formulasini qidirib, haversine bilan Toshkent-Nukus uchun solishtiring (farq ~0.3% atrofida bo'lishi kerak)
- Ekvirektangulyar xatosini masofa (1, 10, 100, 1000 km) va kenglik (0, 41, 70) bo'yicha jadval qiling
- Narx modelida
yol / haversinenisbatini tuman bo'yicha baholang - Azimut bo'yicha safarlar taqsimotini 8 yo'nalishga (Sh, ShSh, ...) bo'lib sanang
Vazifa 4: Indeks
Modellang (2-misol asosida):
- Geohash uzunligi 6 bilan radius so'rovini yozing (o'z katagi + 8 qo'shni) va natijani BallTree bilan solishtiring
- Nuqtalar sonini 200 000 ga oshiring — to'liq ko'rib chiqish, panjara va BallTree masofa hisoblari qanday o'sadi?
- "Har safar uchun 500 m ichidagi o'tgan safarlar soni" belgisini BallTree bilan hisoblang
- Panjara katagini 0.15 km qiling — 3 x 3 yetarlimi? Nega?
Vazifa 5: Poligon va belgilar
Modellang (3-misol asosida):
- Teshikli poligon (masalan, bog') uchun ray casting ni kengaytiring
- Nuqta aynan qirra ustida bo'lsa natija qanday? Qoida kiriting
- Eng yaqin "metro bekati" (5 ta sintetik nuqta) gacha masofa belgisini qo'shing — HistGB va chiziqli modelda ta'siri
sklearn.preprocessing.TargetEncoderbilan out-of-fold TE ni takrorlang
Vazifa 6: Fazoviy CV va klasterlar
Modellang (4-misol asosida):
- Blok o'lchamini 0.5, 1.5, 3, 5 km qiling — blokli CV optimizmi qanday o'zgaradi?
- Maydonning o'lcham ko'lamini (0.8 km) 0.3 va 2 km qiling — Moran I va optimizm
- DBSCAN
epsvamin_samplesni o'zgartirib, issiq nuqtalar soni qanday o'zgarishini ko'ring; HDBSCAN bilan solishtiring - Mahalliy Moran (LISA) ni yozing: har nuqta uchun
z_i * mean(z_qo'shni)— qaysi joylar "issiq"?
Vazifa 7: O'ylash
Yetkazib berish kompaniyasi: "Toshkentda yetkazib berish vaqtini bashorat qilish modelimiz CV da RMSE 4.2 daqiqa berdi. Keyingi oy Samarqandda ishga tushiramiz — o'sha model bilan, xato ham shunday bo'ladi." Nima deysiz?
Javob
Qisqa javob: 4.2 daqiqa — Toshkentdagi ma'lum hududlar uchun baho; Samarqand — yangi hudud, u yerdagi xato bu raqamdan katta bo'lishi deyarli aniq.
1. Qanday CV qilingan? Agar tasodifiy KFold bo'lsa, har test buyurtmasining qo'shnilari o'quvda bo'lgan. 4-misolda bunday baho yangi hudud xatosini muntazam kam ko'rsatdi (+0.076, 8 tanlovda). Yangi shahar uchun esa farq ancha katta bo'ladi: boshqa ko'chalar tarmog'i, boshqa tirbandlik, boshqa zichlik.
2. Belgilar ko'chadimi? lat va lon Samarqandda umuman boshqa qiymatlar — daraxt model ular uchun eng chetdagi bargni ishlatadi (ekstrapolyatsiya yo'q). Ko'chadigan belgilar — nisbiy: masofa (haversine, km), shahar markazigacha masofa, zichlik, soat, tirbandlik. "Toshkent katagi TE" kabi belgilar Samarqandda ma'nosiz.
3. Qanday tekshirish.
# 1) belgilarni shaharga bog'liq bo'lmagan qilish: masofalar, zichlik, vaqt
# 2) fazoviy blokli CV (yoki bir nechta shahar bo'lsa - shahar bo'yicha GroupKFold)
# 3) Samarqandda birinchi haftalar: kichik pilot, haqiqiy xatoni o'lchash
# 4) monitoring va qayta o'qitish (27.11-27.13), mahalliy ma'lumot to'plangachRahbarga javob: "4.2 daqiqa — Toshkentning o'zi uchun. Samarqand uchun xato kattaroq bo'ladi; qanchaligini fazoviy CV bilan baholaymiz, belgilarni shaharga bog'liq bo'lmagan qilamiz va pilot davrida haqiqiy xatoni o'lchab, mahalliy ma'lumot bilan qayta o'qitamiz."
Nimani mustahkamlaydi: 2.2, 2.5, 2.7-bo'limlar.
Xulosa
Bu darsda geografik ma'lumot bilan ishlashni noldan qurdik.
Eng muhim uch fikr:
Koordinata — tekis son emas. Gradus farqlaridan Evklid masofa Toshkentdan boshqa shaharlargacha
+17.8%dan+33.7%gacha xato berdi va undan hisoblangan "1 km narxi" ni ham buzdi (2731o'rniga3308so'm). Haversine noldansklearnbilan aynan mos, shahar ichida esa ekvirektangulyar proyeksiya (0.17%dan kam xato) tekis koordinatalar beradi.Indeks va belgilar — tez va halol. Panjara 300 m radius so'rovlarini to'liq ko'rib chiqishning
0.92%masofa hisobi bilan, BallTree2.96%bilan aynan bir xil natija bilan bajardi; geohash chegarasida 8 m oraliqdagi nuqtalar turli kod oldi. Ray castingmatplotlibbilan nol farq berdi. Katak bo'yicha sizgan maqsadli kodlash CV da eng yaxshi (1.373), testda eng yomon (1.564) chiqdi; out-of-fold versiya halol (1.439va1.438). Chiziqli modelgad_markazbelgisi RMSE ni1.753dan1.474ga tushirdi, daraxtga esa deyarli ta'sir qilmadi.Yaqin nuqtalar o'xshash — baholash dizayni shunga mos bo'lsin. Kutish vaqtida Moran I
0.330(p = 0.01). Yangi hudud uchun tasodifiy KFold xatoni muntazam kam ko'rsatdi (optimizm+0.076,SE 0.022), fazoviy blokli CV esa siljishsiz baholadi (-0.023,SE 0.027); farq+0.100(SE 0.007). DBSCAN haversine bilan uchta issiq nuqtani topdi va tarqoq talabni (87.1%) shovqin sifatida qoldirdi.
Keyingi darsda Katta ma'lumot bilan ishlash: bitta kompyuter xotirasiga sig'maydigan ma'lumot — masalan, millionlab taksi safarlari — bilan qanday ishlash kerak.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!