Vi undersöker hur bergvärme påverkar värdet på ett hus genom att se hur mycket felet i Boolis statistiska värdering skiljer sig mellan hus med bergvärme och hus utan bergvärme. För mer detaljer kring metoden kan du läsa ett tidigare blogginlägg i ämnet på https://jobb.booli.se/blog/posts/18473-statistiska-varderingar-som-matt-pa-forsaljningsresultat. I korthet går metoden ut på att vi gör statistiska värderingar av en stor mängd bostäder och sedan delar upp bostäderna i olika grupper för att se om grupperna skiljer sig åt.
Våra datakällor består av Boolis databas med information om bostäder och värderingar, samt brunnsarkivet från SGU. Brunnsarkivet finns på https://www.sgu.se/grundvatten/brunnar-och-dricksvatten/brunnsarkivet/ och innehåller information om var i landet det finns grävda brunnar och vad brunnarna används till. Vi har gjort antagandet att om en fastighet har en energibrunn så används den brunnen till bergvärme/-kyla.
Vi börjar med att läsa in datan som består av alla försäljningar med tillhörande statistiska värderingar i Sverige mellan 2016-06-01 och 2019-06-01. Sedan matchar vi de försäljningarna till brunnsdatan.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# Read residence to sold map and evaluation data
sold2residence = pd.read_csv('data/sold-res-all.csv')
evaluations = pd.read_csv('data/knn_all_ot_77104_20160601-20190601.csv')
evaluations = evaluations[evaluations['objectType'].isin(('Villa', 'Radhus', 'Parhus', 'Kedjehus'))]
# Merge datasets to map residenceId to an evaluation
evals_with_residences = evaluations.merge(sold2residence[['id', 'residenceId', 'areaId', 'areaName']],
left_on='booliId', right_on='id', how='inner')
Vi har data om brunnar med fastighetsbeteckning, kopplat till Boolis bostads-ID, uppdelade i en fil per län. Så vi itererar varje fil och slår samman dem.
# Read well data
from pathlib import Path
partial_dfs = []
pathlist = Path('data/brunnar/').glob('*.csv')
for path in pathlist:
df = pd.read_csv(str(path))
partial_dfs.append(df)
wells = pd.concat(partial_dfs, axis=0, sort=False)
energy_wells = wells[wells['ANVANDNING'] == 'ENE'].copy()
Brunnsarkivet kan innehålla information om fler än 1 brunn per fastighet. Vi är intresserade av att undersöka om det funnits en energibrunn på fastigheten vid försäljningstillfället, så vi väljer ut den tidigast borrade brunnen och rensar bort information om senare borrade brunnar.
Informationen om vilket datum brunnarna borrades finns inte alltid, och när det finns så skiftar formatet något. Vi får skriva en funktion för att tolka datumen och göra om dem till datetime-objekt.
# Attempt to parse well drill dates
def parse_drill_date(date):
parsed = np.nan
if not np.isnan(date):
datestring = str(int(date))
if len(datestring) == 4:
datestring = '{}0101'.format(datestring)
elif len(datestring) == 6:
datestring = '{}01'.format(datestring)
try:
parsed = pd.to_datetime(datestring)
except:
pass
return parsed
energy_wells['drillDate'] = energy_wells['BORRDATUM'].apply(parse_drill_date)
När vi har datumen på datetime-format kan vi sortera vår dataframe på borrdatum och välja ut den tidigast borrade energibrunnen för varje fastighet.
# Sort by date
energy_wells = energy_wells.sort_values("drillDate", ascending=True)
# Keep only the oldest well for each cadastral
energy_wells = energy_wells.drop_duplicates(subset="cadastral", keep='first')
# Merge datasets
combined = evals_with_residences.merge(energy_wells, on='residenceId', how='left')
display("Antal försäljningar: {}".format(evaluations['booliId'].count()))
display("Antal fastighetsmatchade försäljningar: {}".format(evals_with_residences['booliId'].count()))
display("Antal brunnar: {}".format(wells['BRUNNS_ID'].count()))
display("Antal energibrunnar: {}".format(energy_wells['BRUNNS_ID'].count()))
display("Antal försäljningar efter brunnsmatchning: {}".format(combined['booliId'].count()))
Vi tittar kort på vår data för att verifiera att allt gått bra. Vi kan se att vårt ursprungliga dataset med försäljningspriser och värderingar var på ca 248 000 observationer av husförsäljningar. Efter en matchning mot fastighetsdata har vi kvar ca 227 000 obs.
I brunnsarkivet finns data om totalt ca 662 000 brunnar, varav ca 286 000 är energibrunnar.
Efter en matchning mellan fastighetsbeteckningarna i brunnsarkivet och Boolis försäljningsdata har vi kvar våra 227 000 obs, men nu kompletterad med information energibrunnar.
När vi har försäljningsdatum och borrdatum kan vi beräkna en binär feature som säger om det fanns en borrad brunn vid försäljningstillfället. Vi kallar kolumnen drilled_before_sale
och använder den för vår analys.
# Cast solddate string to datetime object
combined['soldDate'] = pd.to_datetime(combined['soldDate'], format='%Y-%m-%d')
# Assign binary class to objects that had a well when they were sold
combined['has_energy_well'] = (~combined['BRUNNS_ID'].isna()) & (combined['ANVANDNING'] == 'ENE')
combined['drilled_before_sale'] = combined.apply(lambda x: x.soldDate > x.drillDate, axis=1)
Vi gör en grundläggande outlier-filtrering på försäljningspris och värderingsfel.
# Remove outliers based on soldPrice and estimation error
filtered = combined
quantiles = filtered['soldPrice'].quantile([0.01, 0.99])
filtered = filtered[(filtered['soldPrice'] >= quantiles[0.01]) &
(filtered['soldPrice'] <= quantiles[0.99])]
quantiles = filtered['error_percent'].quantile([0.05, 0.95])
filtered = filtered[(filtered['error_percent'] >= quantiles[0.05]) &
(filtered['error_percent'] <= quantiles[0.95])]
display(filtered.groupby('drilled_before_sale')['drilled_before_sale'].count())
Det ger oss totalt ca 200 000 observationer i vårt dataset av husförsäljningar, varav ca 18 000 hade bergvärme när de såldes.
När all data är på plats analyserar vi hur feldistributionen för hela populationen ser ut. Sedan undersöker vi om feldistributionerna skiljer sig åt mellan de olika grupperna. Vi vill också undersöka om det finns någon korrelation mellan förekomsten av bergvärme och geografi, tid och annan bostadsdata.
filtered['error_percent'].hist(bins=20)
display("Median absolute error: {} %".format(filtered['abs_error_percent'].median() * 100))
display(filtered['error_percent'].describe())
Eftersom vi tittar på samtliga småhus i hela landet får vi, trots vår outlier-filtrering, ganska långa svansar med stora värderingsfel. Upp mot 80 % övervärdering eller 40 % undervärdering i de värsta fallen. Felkurvan är centrerad kring 3 %, och standardavvikelsen är ca 24 %, vilket innebär att vi i genomsnitt övervärderar med 3 % och att 2/3 av alla värderingar hamnar inom 24 % från rätt försäljningspris. Medianen av absolutvärdet på felet är ca 13,6 %.
Felkurvan är inte normalfördelad, utan ser snarare lognormal ut. Vi tittar vidare på hur felkurvorna ser ut för de olika klasserna.
f = plt.figure(figsize=(16,4))
f.subplots_adjust(hspace=0.4, wspace=0.4)
for i, label in enumerate(filtered['drilled_before_sale'].unique()):
df = filtered[filtered['drilled_before_sale'] == label]
ax = f.add_subplot(1, 2, i+1)
ax.set_title("Har bergvärme = {} [SEK]".format(label))
df['error_percent'].hist(bins=20)
display("Har bergvärme = {}".format(label))
print("Median absolute error: {} %".format(df['abs_error_percent'].median() * 100))
print(df['error_percent'].describe())
Felkurvorna för båda klasser påminner mycket om varandra, båda har det lognormala utseendet. Felkurvan för de obs som har bergvärme är dock förskjuten till vänster, med ett medelfel på ca -3 %. Det innebär att bostäderna i den klassen sålts för mer än vår värdering. Medianen av absolutfelet är i samma storlek och standardavvikelserna är liknande i båda klasser.
from pandas.plotting import register_matplotlib_converters
register_matplotlib_converters()
drilled = filtered[filtered['drillDate'] >= pd.to_datetime('1976-01-01')]
drilled['drillDate'].hist(bins=30)
Borrdatumen för energibrunnar har spikar vid åren kring 2005 och 2017. Men det är en förhållandevis jämn spridning från början av 2000-talet fram till idag.
import plotly.express as px
import plotly.graph_objects as go
px.set_mapbox_access_token(open(".mapboxtoken").read())
filtered['has_well'] = filtered['drilled_before_sale'].astype('str')
sthlm = filtered[filtered['areaName'] == 'Stockholms län']
fig = px.scatter_mapbox(sthlm,
lat="lat",
lon="lng",
color="has_well",
color_continuous_scale=px.colors.cyclical.IceFire,
size_max=0.3,
zoom=8)
fig.update_layout(mapbox_style='light')
fig.show()
För att inte överbelasta mapbox-klienten med markörer begränsar vi oss till Stockholms län och visualiserar hur den geografiska spridningen av bergvärme ser ut i länet.
Vi kan se att det är en ganska jämn spridning av bergvärmebrunnar över länet, men zoomar man in hittar man tydliga kluster där bergvärme helt saknas. T.ex. finns det ingen försäljning av hus med bergvärme i Bro, Brunna eller Kungsängen. Den här typen av lokala avvikelser förekommer här och var i kartan, både i tätbebyggda områden och i glesbygd. Exempel på mindre tätt bebygda områden som helt (eller till största del) saknar bergvärme är Torö i Nynäshamn, Brevikshalvön i Tyresö och Vätö i Norrtälje.
Att det ser ut så här skulle kunna bero på kommunala bestämmelser, då det är den kommunala miljömyndigheten som ger tillstånd för att borra energibrunnar. Det är inte visualiserat här men liknande mönster går att se i hela landet, med isolerade kluster av försäljningar där det inte finns bergvärme alls. Vi kommer inte göra någon djupare undersökning av varför, utan nöjer oss med att konstatera att förekomsten av bergvärme varierar geografiskt, och att det kan påverka tillförlitligheten i vår analys.
pearson_corr = filtered[[
'drilled_before_sale',
'soldPrice',
'estimatedPrice',
'plotArea',
'livingArea',
'additionalArea',
'rooms',
'constructionYear',
]].corr(method='pearson')
display(pearson_corr['drilled_before_sale'])
De parametrar som har ett korrelationsvärde över 0,1 relaterar till husets storlek. Korrelationen mellan bergvärme och tomtstorlek är nära 0.
Möjligen är det något vanligare med bergvärme för större hus, men med så låga korrelationsvärden måste slutsatsen bli att bergvärme är ungefär lika vanligt förekommande för alla tomtstorlekar och husstorlekar.
Nu undersöker vi hur värderingsfelet varierar beroende på om fastigheterna har bergvärme och gör ett statistiskt test för att bedöma tillförlitligheten. Vi tittar också på hur felen varierar baserat på län.
filtered.groupby('drilled_before_sale').agg(
Antal=('error', len),
Medianfel_SEK=('error', np.median),
Medelfel_SEK=('error', np.mean),
Medianfel_procent=('error_percent', np.median),
Medelfel_procent=('error_percent', np.mean)
)
from scipy.stats import ttest_ind
with_well = filtered[filtered['drilled_before_sale'] == True]
without_well = filtered[filtered['drilled_before_sale'] == False]
display(ttest_ind(with_well['error_percent'], without_well['error_percent']))
filtered.groupby(['areaName', 'drilled_before_sale']).agg(
Antal=('error', len),
Medianfel_SEK=('error', np.median),
Medelfel_SEK=('error', np.mean),
Medianfel_procent=('error_percent', np.median),
Medelfel_procent=('error_percent', np.mean)
)
En jämförelse av alla försäljningar i landet visar att bergvärme är värt ca 115 000 SEK. Ett t-test ger ett p-värde i storleksordning 10-258. Det är en väldigt tydlig indikator på att det finns en skillnad mellan de här två distributionerna.
Grupperat per län kan vi se att siffrorna varierar en del över landet. Gotlands län sticker ut med ca 314 000 SEK undervärdering i medianfel. Men eftersom gruppen av försäljningar med bergvärme bara är 29 hus på Gotland är de siffrorna inte tillförlitliga, utan kan handla om slump.
För övriga 20 län kan vi se återkommande mönster. Om ett hus har bergvärme har det en tendens att överträffa Boolis statistiska värdering vid försäljning. Medianfelet uttryckt i procent varierar mycket, allt från 3 % i Stockholms län till närmre 12 % i Norrbotten. Medianfelet uttryckt i kronor ligger mer konstant. Det tyder på att värdeökningen av att installera bergvärme inte är korrelerad till husets värde, utan till kostnaden av att installationen.
Vi kan tydligt se att förekomsten av bergvärme korrelerar med en förhållandevis stor negativ avvikelse från medelfelet för hela populationen. D.v.s. vi värderar hus med bergvärme för lågt, vilket kan tolkas som att bergvärme är värdehöjande.
Exakt hur värdehöjande varierar över landet. Flest kronor får man i Stockholms och Västmanlands län, minst får man i Värmland och Örebro. Sett till snittet i landet får man ca 115 000 SEK mer för sitt hus om det har bergvärme.
Det kan kan tänkas att förekomsten av bergvärme är korrelerat till andra kvaliteter som vi inte tar hänsyn till i våra statistiska värderingar. En hypotes är att bergvärme indikerar att skicket på huset i övrigt är högt, och att det är det goda skicket som är värt 115 000 SEK, inte bara bergvärmen. Den hypotesen är tyvärr svår att testa p.g.a. brist på data. Att borrdatumen för brunnarna är förhållandevis jämnt utspridda över en 20-årsperiod talar emot att bergvärme skulle korrelera till att huset är nyrenoverat. Men det kan finnas skicksvariationer som att t.ex. de mest nedgångna husen inte förekommer i gruppen av hus som har bergvärme, och att det kan ha en påverkan på felstatistiken som gör att analysen inte blir helt tillförlitlig.
Det finns också en geografisk variation i förekomsten av bergvärme, och det finns en möjlighet att bergvärme är vanligare i områden där vi generellt sett undervärderar.
Givet den stora volymen på datan, och de tydliga resultaten i våra tester, vill vi ändå påstå att bergvärme är värdehöjande. Men exakt hur värdehöjande ner på tusenlappen kan vi inte vara helt säkra på.
Att installera bergvärme kostar ca 150 000 SEK innan ROT-avdrag enligt https://www.byggahus.se/varme/vad-kostar-bergvarme. Efter ROT-avdraget hamnar kostnaden på ca 117 000 SEK vilket ju stämmer väldigt väl överrens med siffrorna vi kom fram till i vår analys. Ovanpå det tillkommer installation av ett vattenburet värmesystem inne i huset (om man inte redan har det) som också kan kosta ganska mycket.
Funderar man på att installera bergvärme behöver man såklart göra en kostnadskalkyl utifrån sitt eget hus och sin egna mark. Men har man möjligheten indikerar vår analys att man kan få tillbaks en stor del av sin investering. Bergvärme brukar ju också sänka de löpande uppvärmningskostnaderna man har, och är bättre för klimatet än många andra uppvärmningsmetoder.