Hvor godt rammer model-vurderingen — og hvor gør den ikke?
Skriv en adresse i Region Hovedstaden — den behøver ikke være til salg. Modellen bruger boligens egne oplysninger og de nærmeste udbudte boligers lokale data (station, støj, naboolag) som stedfæstelse.
Estimatet er lige så usikkert som resten af modellen (se “Typisk afvigelse” ovenfor) — og det kender hverken adressens stand, renoveringer eller udsigt. Stedfæstelsen (station, støj, naboolag) er lånt fra de nærmeste udbudte boliger, ikke målt på selve adressen. Brug det som en indikation, ikke en vurdering.
Den stiplede linje er “model = udbudspris”. Prikker over linjen er udbudt højere end modellen vurderer, prikker under er udbudt lavere. Spredningen omkring linjen er modellens usikkerhed.
Beregnet på boliger modellen faktisk bruger. Få handler i et område giver et mindre pålideligt tal — se antallet.
Nogle boliger er ikke almindelige familieboliger — husbåde uden grund, boliger med hjemfaldspligt, andelslignende handler, nedrivningsklare huse. De trækker modellen skæv, så de udelades af beregningen, og de får ingen vurdering på forsiden: en “under vurdering”-mærkat ville her ligne et røverkøb frem for en anden slags bolig.
“Billig kommune” er ikke grunden til at havne her. Modellen har sit eget niveau for hver af de 28 kommuner, så en helt normal bolig i Halsnæs eller Gribskov til 20.000 kr/m² passer lige så godt som en i København til 80.000. Kolonnen Ift. kommunen viser hver bolig målt mod medianen for netop dens kommune og boligtype: de udeladte ligger typisk på 0,3–0,5× eller 1,5–2× det normale lokalt.
Modellen er en hedonisk regression: den forklarer kvadratmeterprisen (log) ud fra boligens egenskaber — størrelse, antal værelser, byggeår, etage, kælder, grund, energimærke, afstand til S-tog og metro, afstand til indkøb, skole og daginstitution, afstand til motorvej, vand og natur, trafikstøj på facaden (Lden i dB fra Miljøstyrelsens støjkortlægning), boligtype, kommune samt realiserede handler i nabolaget (tinglyste salg, justeret til nutidsniveau og matchet på størrelse og byggeår). Den er ridge-regulariseret, så enkeltfaktorer ikke kan løbe løbsk.
Faktorerne overlapper. Trafikstøj er målt for hver adresse, men gør målt på hele materialet ingen forskel på træfsikkerheden — en støjplaget vej er nemlig allerede billigere i de realiserede nabolagshandler, modellen sammenligner med. Først når de handler tages væk, kan man se støjen bære sin egen vægt. Den står derfor stærkest i områder med få sammenlignelige salg — og dB-tallet på den enkelte bolig er under alle omstændigheder en oplysning, der er værd at kende, uanset hvad den gør ved modellen.
Træfsikkerheden er målt med 5-fold krydsvalidering: modellen trænes på 4/5 af boligerne og vurderer den sidste femtedel, så en bolig aldrig hjælper med at forudsige sig selv. Tallet du ser er derfor et ærligt bud på, hvor galt den typisk rammer på en ny bolig — ikke hvor godt den passer til dem, den allerede har set. Efter første kørsel gentages den uden de mest atypiske boliger (robust genberegning), så enkelte særtilfælde ikke trækker hele niveauet.
Hvad modellen ikke kan se: stand og vedligehold, renoveringer, udsigt, planløsning, støj fra naboer, lys og orientering, haven, eller hvor godt boligen er præsenteret. Det er præcis den slags, der afgør de sidste ti-tyve procent — og forklarer hvorfor afvigelsen aldrig kommer ned i nul. Brug tallet som en indikation, ikke som en vurdering: det er ikke en ejendomsmæglervurdering, og det er ikke rådgivning.
Det følgende er den præcise matematik bag afsnittet ovenfor — samme model, ned til hver formel. Koden er i
model.js, delt uændret mellem forsiden og denne side.
Modellen fitter y = ln(kr/m²), ikke prisen direkte — så en afvigelse på 10 % vejer ens for en ejerlejlighed til 3 mio. og en villa til 15 mio., i stedet for at store boliger dominerer fejlmålet.
Hver bolig bliver til en vektor x. Basisdelen er altid med; resten kun når den pågældende datakilde findes i byggets datasæt (alt-eller-intet pr. bygning, ikke pr. bolig).
| Feature | Formel | Betingelse |
|---|---|---|
| Konstant | 1 | altid |
| Areal | ln(a) | altid |
| Areal² | ln(a)² | altid — fanger ikke-lineær kr/m²-effekt af størrelse |
| Værelser | r | altid, 0 hvis ukendt |
| Værelsesstørrelse | a / (40·r) | altid, 0 hvis a eller r mangler |
| Byggeår | (år − 1970) / 50 | altid, 1970 (dvs. 0) hvis ukendt |
| Etage | etage / 5 | kun ejerlejlighed (villa = 0); medianetagen hvis ukendt |
| Kælder | 1[kælder > 0] | altid |
| Grund | ln(grund + 1) / 10 | kun villa (en lejligheds »grund« er hele matriklen); mediangrunden hvis ukendt |
| Energimærke | rang(e) / 7 | altid — A=7…G=1, D-niveau (4) hvis ukendt |
| Afstand til S-tog | ln(dS-tog + 1) / 10 | altid |
| Tæt på S-tog | 1[nær S-togsstation] | altid |
| Boligtype | 1[villa] | altid — ejerlejlighed er basislinjen |
| Altan/terrasse | 1[altan] | altid |
| Afstand til metro | ln(dmetro + 1) / 10 | kun hvis byggets datasæt har metrodata |
| Tæt på metro | 1[≤ 500 m fra metro] | samme betingelse som ovenfor |
| Lokalt benchmark | ln(comp) / 12 | kun hvis realiserede handler findes — se “Lokalt benchmark” nedenfor |
| Afstand til indkøb/skole/institution | ln(d + 1) / 10 ×3 | kun hvis POI-data findes (#7) |
| Afstand til motorvej/vand/sø/natur | ln(d + 1) / 10 ×4 | kun hvis geo-data findes (#8) |
| Vandkant-effekt | exp(−dkyst / 150) | samme betingelse — et fald over ~150 m, ikke en lige linje: forskellen på første og anden række til vandet er et spring, ikke en gradient |
| Vandkant × villa | exp(−dkyst / 150) · 1[villa] | samme — en villa med have til vandet er mere værd end en lejlighed med udsigt |
| Trafikstøj | (dB − 55) / 10 | kun hvis støjdata findes — se forbeholdet ovenfor |
| Kommune | 1[kommune = k] × (antal kommuner − 1) | altid — én kommune er basislinjen, for ikke at kollidere med konstanten |
Lokalt benchmark er ikke en rå gennemsnitspris i området: hvert salg er tidsjusteret til i dag med regionens eget kvartalsindeks, matchet på boligtype, størrelse og byggeår, og de nærmeste salg vægtes efter afstand frem for en hård radius — så et tyndt datagrundlag falder gradvist tilbage på kommune-/typemedianen i stedet for at springe til en 2 km-median. Konkret er det en vægtet median af de K nærmeste tidsjusterede salg, med vægt w aftagende i afstand d (halveret ved 500 m):
Løses direkte ved Gauss-elimination med delvis pivotering (partial pivoting), ikke gradient descent — designmatricen er lille nok (typisk 15–55 kolonner) til at det er både hurtigere og eksakt. Ridge-leddet λΣβⱼ² trækker koefficienterne mod 0, så en enkelt, tyndt repræsenteret feature ikke kan få et vilkårligt stort udslag på et lille datagrundlag.
Boligerne deles i 5 lige store, faste grupper. For hver gruppe trænes modellen uden den gruppe og forudsiger så netop de udeladte boliger — en bolig er derfor aldrig med til at forudsige sig selv:
“Typisk afvigelse” er medianen af den absolutte procentfejl på tværs af alle fem grupper (hver bolig scoret af den model, der ikke så den under træning); usikkerhedsspændet er 10. og 90. percentil af de samme residualer:
Forklaringsgraden (R²) nedenfor er ikke beregnet krydsvalideret — se forbeholdet nederst.
Modellen fittes først på alle boliger; residualerne y − Xβ₀ giver en median m₀ og en MAD (median absolut afvigelse fra medianen). Faktoren 1,4826 skalerer MAD til at svare til én standardafvigelse under en normalfordeling:
Modellen genberegnes på resten. Det er præcis de boliger, der vises i “Boliger modellen holder udenfor” ovenfor.
R² beregnes på det endelige, robuste fit — altså hvor godt modellen rammer boliger, den selv har trænet på (mængden keep), ikke nye boliger den ikke har set:
Det er derfor et andet tal end “typisk afvigelse” (som er krydsvalideret) og kan ikke bruges som et mål for reel træfsikkerhed alene. Flere features vil næsten altid løfte R² lidt, selv når de reelt intet bidrager med — jo flere frihedsgrader, jo bedre kan modellen tilpasse sig data den allerede kender. Det er præcis derfor krydsvalideret “typisk afvigelse”, ikke R², er det tal der afgør om en ny feature bliver i modellen eller fjernes igen.