Machine Learning pl

Analiza cen mieszkań w Warszawie — regresja liniowa

Odkąd pamiętam, zawsze interesowałem się rynkiem nieruchomości. Przyczyniła się do tego moja częsta zmiana mieszkań oraz fakt, że za każdym razem wszelkie formalności sprzedaży i kupna przeprowadzałem samodzielnie. Poszukiwanie dobrej lokalizacji to proces niezwykle ekscytujący, ale też i czasochłonny. Oprócz zdobycia rozeznania w cenach, zaznajamiałem się z topografią miasta oraz na własnej skórze przekonywałem, jak istotny jest czynnik dobrej komunikacji do centrum.

Ostatnio postanowiłem przeprowadzić analizę cen mieszkań z użyciem algorytmów uczenia maszynowego i przekonać się, czy dysponując surowymi danymi, można odnaleźć zależności jakie kształtują ostateczną wartość nieruchomości.

Idealnym modelem przy tego typu rozważaniach byłaby regresja liniowa. Jest to stosunkowo prosty algorytm, który potrafi znaleźć relacje pomiędzy cechami obiektu  — czyli zmiennymi objaśniającymi a odpowiedzią (czyli ostateczną ceną).

Najprostszym przypadkiem, jest prosta regresja liniowa. W takim modelu, tylko jedna zmienna objaśniająca wpływa na ostateczną odpowiedź (w naszym przypadku cenę). Możemy oczywiście uogólnić model do regresji wielowymiarowej, gdzie ostateczną odpowiedź kształtuje wiele czynników.

Korzystając ze scrapera, jaki stworzyłem przy okazji pisania artykułu Jak nauczyć program rozumienia mowy potocznej, pozyskałem bazę około 850 nieruchomości wraz z cechami takimi jak liczba pokoi, powierzchnia, dzielnica, cena za metr oraz za całą nieruchomość. Posiadałem więc dane o zdecydowanej większości mieszkań sprzedawanych w owym czasie w obrębie Warszawy.

Jakość danych.

Przechodząc do konkretów, korzystając ze struktury DataFrame z biblioteki Pandas wyświetlmy te dane:

import pandas as pd

df = pd.read_csv('C:\\UczenieMaszynowe\\mieszkania.csv', header=None, sep=';')
df.columns = ['DZIELNICA', 'CENA', 'CENA_ZA_M2', 'LICZBA_POKOI', 'POWIERZCHNIA']

df

i posortujmy je po kolumnie CENA_ZA_M2:

df.sort_values("CENA_ZA_M2")

Jak widać, część rekordów jest uszkodzona i posiada wartości typu NaN (“Not a Number”) — wartość numerycznego typu danych oznaczająca niezdefiniowaną wielkość.

Pozbądźmy się bezwartościowych wierszy danych, korzystając z metody dropna obiektu DataFrame:

df = df.dropna()

Pozostało jeszcze 825 rekordów, więc nadal sporo.

Możemy teraz zapoznać się z typami kolumn. W problematycznych przypadkach, można je nawet jawnie skonwertować używając kodu:

df = df.astype({'LICZBA_POKOI':'int'})

Pobieżna analiza pokazuje, że Praga-Południe, Ursus, Wesoła i Białołęka, to najtańsze dzielnice, natomiast w Śródmieściu ceny osiągają 30 000 a w trzech przypadkach nawet ponad 40 000 za m²! Ostatni rekord, w którym mieszkanie kosztuje 20 mln., przy cenie za metr kwadratowy ponad 100 000 zł, to prawie 200 metrowe mieszkanie w inwestycji Złota 44 (Żagiel).

Eksploracja danych

Utwórzmy teraz macierz rozproszenia i sprawdźmy, czy dane można ze sobą automatycznie skorelować, czyli czy któraś ze zmiennych objaśniających wpływa na odpowiedź (czyli cenę).

W tym celu wykorzystajmy funkcję pairplot z biblioteki seaborn.

import matplotlib.pyplot as plt
import seaborn as sns

sns.set(style='whitegrid', context='notebook')
cols = [ 'CENA_ZA_M2', 'LICZBA_POKOI', 'POWIERZCHNIA', 'CENA']

sns.pairplot(df[cols], height=2.5)
plt.show()

Widać, że istnieje liniowy związek pomiędzy cechami:

  • POWIERZCHNIA i CENA,
  • CENA_ZA_METR i CENA

Korzystając z funkcji biblioteki seaborn, wygenerujmy teraz macierz rozproszenia w postaci mapy cieplnej.

import numpy as np

cm = np.corrcoef(df[cols].values.T)
sns.set(font_scale=1.5)

hm = sns.heatmap(cm, 
                 cbar=True, 
                 annot=True,square=True,  
                 fmt='.2f',
                 annot_kws={'size': 15}, 
                 yticklabels=cols, 
                 xticklabels=cols)
plt.show()

Mapa potwierdza korelację pomiędzy POWIERZCHNIA i CENA (0,82), oraz CENA_ZA_METR i CENA (0,69). Są to bowiem wartości bliskie liczby 1.

Postanowiłem wzbogacić dane i szukać dalszych cech mogących wpłynąć na ostateczną cenę nieruchomości. W tym celu, posiłkując się danymi ze strony Panorama dzielnic Warszawy w 2019 r [1]. dodałem do każdej nieruchomości, sumaryczną liczbę przestępstw w obrębie dzielnicy.

To okazało się być ślepą uliczką i nie znalazłem żadnego powiązania pomiędzy tymi danymi. Po zastanowieniu, doszedłem do wniosku, że przecież wysoka przestępczość jest zarówno na Pradze-Południe jak i w Śródmieściu a dzielnice te znacznie różnią się od siebie pod względem ceny za m² nieruchomości.

Drążąc temat dalej, ponownie skorzystałem ze scrapera, ale tym razem pobrałem z kodu portalu wartości latitude i longitude wybranych nieruchomości, służące do zaznaczenia na mapie lokalizacji.

"location":{"latitude":52.2856221,"longitude":21.0445192

Następnie, posiłkując się mapami Google, określiłem orientacyjny czas dojazdu do Śródmieścia z kilkudziesięciu losowo wybranych mieszkań.

Dysponując tak niewielką próbką danych, udało mi się stwierdzić, że wartość CENA_ZA_M2 jest odwrotnie proporcjonalna do liczby minut dojazdu do centrum. Czyli wraz ze wzrostem czasu dojazdu, cena nieruchomości maleje.

Regresja liniowa

Przejdźmy do analizy regresji liniowej. Skorzystajmy w tym celu z obiektu LinearRegression bibioteki scikit-learn.

from sklearn.linear_model import LinearRegression
import numpy as np
import matplotlib.pyplot as plt

Zmiennym X i y przypiszmy wartości pól POWIERZCHNIA i CENA.

X = df[['POWIERZCHNIA']].values
y = df['CENA'].values

Ale tym razem popracujmy z danymi standaryzowanymi, które finalnie przypiszmy do nowych zmiennych X_std i y_std.

Standaryzacja danych do modeli machine learning polega na przekształceniu danych pierwotnych, aby ich rozkład miał średnią wartość równą 0 i odchylenie standardowe równe 1.

Zabieg taki stosuje się wtedy, kiedy korelujemy ze sobą zmienne będące w zupełnie różnych skalach wielkości. Przykładowo, jedna zmienna oscyluje pomiędzy wartościami od 1 do 10 a druga w granicach milionów, czyli o kilka rzędów wielkości wyższych.

Po wystandaryzowaniu zmiennych, skala ich wartości przestaje mieć znaczenie, a zaczynie ma jedynie ich rozrzut, czyli wariancja.

Należy zauważyć, że standaryzacja danych nie gwarantuje, że będą one mieścić się w zakresie od 0 do 1.

Standaryzację należy przeprowadzić z użyciem klasy StandardScaler z biblioteki scikit-learn.

Wyświetlmy teraz kolumnę POWIERZCHNIA przed standaryzacją:

i po standaryzacji:

Przejdźmy teraz do wyznaczenia linii regresji:

from sklearn.preprocessing import StandardScaler

sc_x = StandardScaler()
sc_y = StandardScaler()
X_std = sc_x.fit_transform(X)
y_std = sc_y.fit_transform(y[:, np.newaxis]).flatten()
slr = LinearRegression()
slr.fit(X_std, y_std)
y_pred = slr.predict(X)

print('Nachylenie: %.3f' % slr.coef_[0])
print('Punkt przeciecia: %.3f' % slr.intercept_)

Nachylenie: 0.837
Punkt przecięcia: 0.000

def lin_regplot(X, y, model):
    plt.scatter(X, y, c='lightblue')
    plt.plot(X, model.predict(X), color='red', linewidth=2)    
    return

lin_regplot(X_std, y_std, slr)

plt.xlabel('POWIERZCHNIA')
plt.ylabel('CENA')

plt.tight_layout()
plt.show()

Dzięki powyższemu algorytmowi, otrzymaliśmy wykres zależności wystandaryzowanych cen mieszkań od ich wystandaryzowanych powierzchni, z naniesioną prostą regresji liniowej.

Gdybyśmy jednak pozostali przy surowych danych — które nadal dostępne są w zmiennych X i y, (ceny mieszkań w milionach zł i powierzchnia w m²), wykres stałby się nieco bardziej czytelny.

slr = LinearRegression()
slr.fit(X, y)
y_pred = slr.predict(X)

print('Nachylenie: %.3f' % slr.coef_[0])
print('Punkt przeciecia: %.3f' % slr.intercept_)

Nachylenie: 20484.317
Punkt przecięcia: -235808.352

lin_regplot(X, y, slr)

plt.xlabel('POWIERZCHNIA')
plt.ylabel('CENA')

plt.tight_layout()
plt.show()

Jak widać, na osi CENA, wartości podane są w milionach zł a POWIERZCHNIA w m². Standaryzację danych należy więc używać wtedy, kiedy jest ona wskazana.

W powyższym artykule pokazałem, jak używając algorytmów uczenia maszynowego, znaleźć zależności kształtujące ceny nieruchomości.

Algorytm ten ma zastosowanie również w innych dziedzinach takich jak ekonomia, socjologia czy biznes.


Źródła:

[1] Panorama dzielnic Warszawy w 2019 r. https://warszawa.stat.gov.pl/publikacje-i-foldery/inne-opracowania/panorama-dzielnic-warszawy-w-2019-r-,5,21.html

Repozytorium GitHub Dr. Sebastiana Raschka https://github.com/rasbt