Malta Property for Sale - Q2 vs Q4 2016 In-depth Analysis

39 minute read

In this report we will analyse property for sale in Malta to compute and visualise statistics such as average prices, by property type, town and region. Property price trends are also determined by comparing two datasets, collected six months apart.

Data Collection

The property for sale data was collected by scraping publicly available information from a leading real estate agency in Malta. To determine property price trends, two datasets were collected six months apart, one in April 2016 and another in October 2016.

The data collected was further processed to remove data inconsistencies and missing data. To remove noisy data points for which we do not have enough data to compute meaningful statistics, we removed properties from towns if there were less than three properties of that type in a particular town.

Dataset Statistics

from __future__ import division

%matplotlib inline
import matplotlib.pyplot as plt
import numpy
import pandas
import seaborn
import textwrap

# set some CSS styles to customize display of pandas Dataframes as tables.
from IPython.display import HTML, display
display(HTML('''<style>
                    .hide_side_header thead tr th:first-child {visibility:hidden;}
                    .hide_side_header tbody tr th {visibility:hidden;}
                    .right_aligned td { text-align: right; }
                </style>'''))

# set overall font scale to use in charts
seaborn.set(font_scale=1.5);
seaborn.set_style("whitegrid");

# lambda function used in dataframe to html formatters
lambda_format_thousands = lambda x: '{:,}'.format(x)

def wrap_labels(axis, x_axis=True):
    if x_axis:
        tick_labels = axis.get_xticklabels();
    else:
        tick_labels = axis.get_yticklabels();

    wrapper = textwrap.TextWrapper()
    wrapper.width = 7
    wrapper.break_long_words = False
    
    tick_labels = [l.get_text().title() for l in tick_labels]
    tick_labels = ['\n'.join(wrapper.wrap(l)) for l in tick_labels]
    
    if x_axis:
        axis.set_xticklabels(tick_labels)
    else:
        axis.set_yticklabels(tick_labels)
    
def plot_chart_helper(x_field_label, y_field_label, chart_title, chart_data, axes):
    '''
    Small helper to plot seaborn.barplot.
    '''
    seaborn.set_style('whitegrid');
    seaborn.barplot(x=x_field_label, y=y_field_label, data=chart_data, palette="BuGn_d", ax=axes);
    axes.set_ylabel(y_field_label);
    axes.set_xlabel(x_field_label);
    axes.set_title(chart_title);
    wrap_labels(axes);

property_Q2 = pandas.read_csv('/home/jovyan/work/machine learning/ds/projects/property-analysis/webapp/pa-api/v1.0/data/malta/mt01/sale/20160414222000/clean_data.csv')
property_Q4 = pandas.read_csv('/home/jovyan/work/machine learning/ds/projects/property-analysis/webapp/pa-api/v1.0/data/malta/mt01/sale/20161022125843/clean_data.csv')

# title case locations
property_Q2['location'] = property_Q2['location'].str.title()
property_Q4['location'] = property_Q4['location'].str.title()

num_properties_in_set1 = len(property_Q2)
num_properties_in_set2 = len(property_Q4)
difference_in_properties = num_properties_in_set1 - num_properties_in_set2

print "Q2 2016: {0} properties".format(num_properties_in_set1)
print "Q4 2016: {0} properties".format(num_properties_in_set2)

if difference_in_properties < 0:
    print "Latest data set has {0} properties more than the previous data set - ({1:.1f}%).".format(
          abs(difference_in_properties),(abs(difference_in_properties)/num_properties_in_set2)*100)
elif difference_in_properties > 0:
    print "Previous data set has {0} properties more than the latest data set - ({1:.1f}%).".format(
          abs(difference_in_properties),(abs(difference_in_properties)/num_properties_in_set1)*100)
Q2 2016: 4525 properties
Q4 2016: 4098 properties
Previous data set has 427 properties more than the latest data set - (9.4%).
property_Q2['dataset_id'] = '2016Q2'
property_Q4['dataset_id'] = '2016Q4'
property_Q2_Q4 = pandas.concat([property_Q2, property_Q4])

f, (ax1) = plt.subplots(1, 1, figsize=(14, 6))
property_Q2_Q4.sort_values(by=['dataset_id','property_type'], inplace=True)
seaborn.countplot(x='property_type', hue='dataset_id', data=property_Q2_Q4, palette="Greens_d", ax=ax1);
ax1.set_xlabel('Property Type');
ax1.set_ylabel('Property Count');
ax1.set_title('Distribution of Property Types - Q2 vs Q4 (2016)');
wrap_labels(ax1)

png

Notes

  • We have no way to validate the prices at which properties are listed for sale. These can vary from the actual final purchase price for various reasons, however, we feel that the data still gives a fair view of current market trends.

  • Since the data was collected from one real estate agent’s publicly available records it might not be representative of the overall property market. Nonetheless, in the dataset we have found a good quantity of properties spread out all over the islands, refer to distribution charts by location below, and so think the data is representative enough.

  • Finally, the estate agency selected is not in the business of promoting high end exclusive properties only and so the price ranges for all property types are well represented.

Distribution of Property in Malta

seaborn.set(font_scale=1.25);
seaborn.set_style('whitegrid');

f, (ax1) = plt.subplots(1, 1, figsize=(20,7))
location_order = property_Q4.groupby('location').count().sort_values(by='price', ascending=False).index.values
seaborn.countplot(x="location", data=property_Q4, order=location_order, palette="Greens_d", ax=ax1);
ax1.set_xlabel('Location');
ax1.set_ylabel('Property Count');
ax1.set_title('Distribution of Property Across Malta - Q4 (2016)');
plt.setp(ax1.get_xticklabels(), rotation=90);

seaborn.set(font_scale=1.5);

png

def plot_top_availability_towns(property_type, num_towns=10):
    property_type_list = ['apartment','penthouse','maisonette','town house',
                          'terraced house','villa','bungalow']
    if property_type not in property_type_list:
        print "property_type must be one of {0}.".format(property_type_list)
        return
    
    seaborn.set_style('whitegrid');
    f, (ax1) = plt.subplots(1, 1, figsize=(20,7))
    filtered_data = property_Q4[property_Q4['property_type'] == property_type]
    filtered_data = filtered_data.drop(['beds','baths','property_type','status','type','dataset_id'], 1)
    filtered_data = filtered_data.rename(columns = {'price':'count'})
    filtered_data = filtered_data.groupby('location', as_index=False).count().sort_values(by='count', ascending=False)
    seaborn.barplot(x="location", y='count', data=filtered_data.head(n=num_towns), palette="Greens_d", ax=ax1);
    ax1.set_xlabel('Location');
    ax1.set_ylabel('Property Count');
    ax1.set_title('Top {0} Towns for {1}s for Sale - Q4 2016'.format(num_towns, property_type.title()));

Top Towns Based on Availability of a Particular Property Type

plot_top_availability_towns('apartment')

png

plot_top_availability_towns('penthouse')

png

plot_top_availability_towns('maisonette')

png

plot_top_availability_towns('town house')

png

plot_top_availability_towns('terraced house')

png

plot_top_availability_towns('villa')

png

plot_top_availability_towns('bungalow', num_towns=5)

png

Country Level Analysis

Next, we take a look at the distribution of prices per property type across the whole archipelago, and compare them across datasets to gain insight into any price movements in the property market.

f, (ax1) = plt.subplots(1, 1, figsize=(14,6))
property_Q2_Q4.sort_values(by=['dataset_id','property_type'], inplace=True)
seaborn.set_style('whitegrid');
seaborn.boxplot(x="property_type", y="price", hue="dataset_id", data=property_Q2_Q4, palette="Greens_d", ax=ax1);
ax1.set_yscale('log');
ax1.set_xlabel('Property Type');
ax1.set_ylabel('Price in Euro (Log Scale)');
ax1.set_title('Malta Property Price Distribution by Property Type - Q2 vs Q4 (2016)');
wrap_labels(ax1)

png

The above plot shows a clear upward shift in prices across the board, in particular for apartments, penthouses, town houses, maisonettes and bungalows, and to a much lesser extent terraced houses and villas. Considering this plot is using a log scale on the y-axis, the upward price movement is quite significant, even more so having occured in a brief six month period.

To quantify this we will compute the mean and median price for each property type, compare them across datasets and compute the percentage difference. We will then plot bar charts to visualise this information.

Q4_mean_prices_per_property_type = property_Q4.groupby('property_type', as_index=False)['price'].mean()
Q2_mean_prices_per_property_type = property_Q2.groupby('property_type', as_index=False)['price'].mean()
property_type_mean_price_difference = pandas.DataFrame(Q4_mean_prices_per_property_type['property_type'])
property_type_mean_price_difference.columns = ['Property Type']
property_type_mean_price_difference['Absolute Change (Euro)'] = Q4_mean_prices_per_property_type['price'] - Q2_mean_prices_per_property_type['price']
property_type_mean_price_difference['Percentage Change'] = numpy.round((property_type_mean_price_difference['Absolute Change (Euro)'] / Q2_mean_prices_per_property_type['price']) * 100, 2)

Q4_median_prices_per_property_type = property_Q4.groupby('property_type', as_index=False)['price'].median()
Q2_median_prices_per_property_type = property_Q2.groupby('property_type', as_index=False)['price'].median()
property_type_median_price_difference = pandas.DataFrame(Q4_median_prices_per_property_type['property_type'])
property_type_median_price_difference.columns = ['Property Type']
property_type_median_price_difference['Absolute Change (Euro)'] = Q4_median_prices_per_property_type['price'] - Q2_median_prices_per_property_type['price']
property_type_median_price_difference['Percentage Change'] = numpy.round((property_type_median_price_difference['Absolute Change (Euro)'] / Q2_median_prices_per_property_type['price']) * 100, 2)

def plot_mmppc_by_pt(display_charts=True, display_tables=False, hspace=.3, wspace=.125):
    '''
    Plot mean and median price percentage change for each property type across the whole dataset.
    '''
    
    if display_charts:
        f, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(20,15))
        f.subplots_adjust(hspace=hspace, wspace=wspace);
        plt.suptitle('Malta Property - Price Changes per Property Type - Q4 vs Q2 (2016)', 
                     fontsize=20, verticalalignment='bottom');
    
    # mean price percentage change    
    
    if display_charts:
        property_type_mean_price_difference.sort_values(by='Absolute Change (Euro)', ascending=False, inplace=True)
        plot_chart_helper('Property Type', 'Absolute Change (Euro)', 'Mean Price - Ordered by Absolute Change',
                          property_type_mean_price_difference, ax1);
        property_type_mean_price_difference.sort_values(by='Percentage Change', ascending=False, inplace=True)
        plot_chart_helper('Property Type', 'Percentage Change', 'Mean Price - Ordered by Percentage Change',
                          property_type_mean_price_difference, ax2);
    
    if display_tables:
        print "Mean price percentage change per property type"
        property_type_mean_price_difference.sort_values(by='Percentage Change', ascending=False, inplace=True)
        display(HTML(property_type_mean_price_difference.to_html(formatters={'Absolute Change (Euro)':lambda_format_thousands}, classes='right_aligned hide_side_header')))
        print
    
    # median price percentage change
    
    if display_charts:
        property_type_median_price_difference.sort_values(by='Absolute Change (Euro)', ascending=False, inplace=True)
        plot_chart_helper('Property Type', 'Absolute Change (Euro)', 'Median Price - Ordered by Absolute Change',
                          property_type_median_price_difference, ax3);
        property_type_median_price_difference.sort_values(by='Percentage Change', ascending=False, inplace=True)
        plot_chart_helper('Property Type', 'Percentage Change', 'Median Price - Ordered by Percentage Change',
                          property_type_median_price_difference, ax4);
    
    if display_tables:
        print "Median price percentage change per property type"
        property_type_median_price_difference.sort_values(by='Percentage Change', ascending=False, inplace=True)
        display(HTML(property_type_median_price_difference.to_html(formatters={'Absolute Change (Euro)':lambda_format_thousands}, classes='right_aligned hide_side_header')))

Price Changes per Property Type - Q4 vs Q2 2016

View Tables

plot_mmppc_by_pt()

png

Bungalows, town houses, apartments and penthouses have seen the greatest increases both in absolute values and percentage terms, using both average measures. In contrast, the mean price for villas has fallen by -1.15% (€15,269), while that of terraced houses increased marginally by 1.8% (€6,141). Using the median measure, villas and terraced houses also registered very modest increases of 2.63% (€25,000) and 1.75% (€5,596) respectively.

Town Level Analysis

We will now look into the data at town level, to establish whether these price trends are present across Malta and Gozo or whether they are limited to specific regions, which might shed light on the market forces at work.

Q4_mean_prices_per_location_property = property_Q4.groupby(['location','property_type'], as_index=False)['price'].mean()
Q2_mean_prices_per_location_property = property_Q2.groupby(['location','property_type'], as_index=False)['price'].mean()
mean_results = Q2_mean_prices_per_location_property.merge(Q4_mean_prices_per_location_property, on=['location','property_type'])
mean_results = mean_results.rename(columns = {'location':'Location', 'property_type':'Property Type', 'price_x':'Mean Q2 2016', 'price_y':'Mean Q4 2016'})
mean_results['Absolute Change (Euro)'] = mean_results['Mean Q4 2016'] - mean_results['Mean Q2 2016']
mean_results['Percentage Change'] = numpy.round((mean_results['Absolute Change (Euro)'] / mean_results['Mean Q2 2016']) * 100,2)

Q4_median_prices_per_location_property = property_Q4.groupby(['location','property_type'], as_index=False)['price'].median()
Q2_median_prices_per_location_property = property_Q2.groupby(['location','property_type'], as_index=False)['price'].median()
median_results = Q2_median_prices_per_location_property.merge(Q4_median_prices_per_location_property, on=['location','property_type'])
median_results = median_results.rename(columns = {'location':'Location', 'property_type':'Property Type', 'price_x':'Median Q2 2016', 'price_y':'Median Q4 2016'})
median_results['Absolute Change (Euro)'] = median_results['Median Q4 2016'] - median_results['Median Q2 2016']
median_results['Percentage Change'] = numpy.round((median_results['Absolute Change (Euro)'] / median_results['Median Q2 2016']) * 100,2)

def plot_mmppc_by_ptat(property_type, display_charts=True, display_tables=False, num_towns=5, hspace=.3, wspace=.2):
    '''
    Plot mean and median price percentage change for the property type specified, for each town in the dataset.
    '''
    
    if display_charts:
        f, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(20,15))
        f.subplots_adjust(hspace=hspace, wspace=wspace);
        plt.suptitle('Malta Property - {0} Price Changes per Town - Q4 vs Q2 (2016)'.format(property_type.title()), fontsize=20, verticalalignment='bottom');
    
    # mean price percentage change
    
    property_type_mean = mean_results[mean_results['Property Type'] == property_type].drop('Property Type', 1)
    sorted_property_type_mean = property_type_mean.sort_values(by='Percentage Change', ascending=False)

    if display_charts:
        plot_chart_helper('Location', 'Percentage Change', 'Top {0} Towns for Mean Price Percentage Change'.format(num_towns),
                          sorted_property_type_mean.head(n=num_towns), ax1);
    
        plot_chart_helper('Location', 'Percentage Change', 'Bottom {0} Towns for Mean Price Percentage Change'.format(num_towns),
                          sorted_property_type_mean.tail(n=num_towns), ax2);
    
    if display_tables:
        print "{0} - Top {1} locations - Largest mean price percentage change".format(property_type, num_towns)
        display(HTML(sorted_property_type_mean.head(n=num_towns).to_html(formatters={'Absolute Change (Euro)':lambda_format_thousands,
                                                                              'Mean Q2 2016':lambda_format_thousands,
                                                                              'Mean Q4 2016':lambda_format_thousands},
                                                                  classes='right_aligned hide_side_header')))
        print
        print "{0} - Bottom {1} locations - Smallest mean price percentage change".format(property_type, num_towns)
        display(HTML(sorted_property_type_mean.tail(n=num_towns).to_html(formatters={'Absolute Change (Euro)':lambda_format_thousands,
                                                                              'Mean Q2 2016':lambda_format_thousands,
                                                                              'Mean Q4 2016':lambda_format_thousands},
                                                                  classes='right_aligned hide_side_header')))
        print
    
    # median price percentage change
    
    property_type_median = median_results[median_results['Property Type'] == property_type].drop('Property Type', 1)
    sorted_property_type_median = property_type_median.sort_values(by='Percentage Change', ascending=False)

    if display_charts:
        plot_chart_helper('Location', 'Percentage Change', 'Top {0} Towns for Median Price Percentage Change'.format(num_towns),
                          sorted_property_type_median.head(n=num_towns), ax3);

        plot_chart_helper('Location', 'Percentage Change', 'Bottom {0} Towns for Median Price Percentage Change'.format(num_towns),
                          sorted_property_type_median.tail(n=num_towns), ax4);
    
    if display_tables:
        print "{0} - Top {1} locations - Largest median price percentage change".format(property_type, num_towns)
        display(HTML(sorted_property_type_median.head(n=num_towns).to_html(formatters={'Absolute Change (Euro)':lambda_format_thousands,
                                                                                'Median Q2 2016':lambda_format_thousands,
                                                                                'Median Q4 2016':lambda_format_thousands},
                                                                    classes='right_aligned hide_side_header')))
        print
        print "{0} - Bottom {1} locations - Smallest median price percentage change".format(property_type, num_towns)
        display(HTML(sorted_property_type_median.tail(n=num_towns).to_html(formatters={'Absolute Change (Euro)':lambda_format_thousands,
                                                                                'Median Q2 2016':lambda_format_thousands,
                                                                                'Median Q4 2016':lambda_format_thousands},
                                                                    classes='right_aligned hide_side_header')))

Apartment Price Changes per Town - Q4 vs Q2 2016

View tables

plot_mmppc_by_ptat('apartment')

png

Penthouse Price Changes per Town - Q4 vs Q2 2016

View tables

plot_mmppc_by_ptat('penthouse')

png

Maisonette Price Changes per Town - Q4 vs Q2 2016

View tables

plot_mmppc_by_ptat('maisonette')

png

Town House Price Changes per Town - Q4 vs Q2 2016

View tables

plot_mmppc_by_ptat('town house')

png

Terraced House Price Changes per Town - Q4 vs Q2 2016

View tables

plot_mmppc_by_ptat('terraced house')

png

Villa Price Changes per Town - Q4 vs Q2 2016

View tables

plot_mmppc_by_ptat('villa')

png

Bungalow Price Changes per Town - Q4 vs Q2 2016

View tables

plot_mmppc_by_ptat('bungalow', hspace=.4)

png

def boxplot_chart_helper(x_field_label, y_field_label, chart_title, chart_data, chart_order, plot_swarm=True, y_max_lim=0, top=True, num_towns=5):
    '''
    Small helper to plot seaborn.boxplot, with an optional overlayed swarm plot.
    '''
    
    if top:
        boxplot_colour = '#FF3333'
        swarmplot_colour = '#333333'
    else:
        boxplot_colour = '#80CCFF'
        swarmplot_colour = '#333333'
    
    f, (ax1) = plt.subplots(1, 1, figsize=(20,7))
    seaborn.set_style('whitegrid');
    seaborn.boxplot(x=x_field_label, y=y_field_label, 
                    data=chart_data, order=chart_order, 
                    color=boxplot_colour, ax=ax1, showfliers=False);
    
    if plot_swarm:
        seaborn.swarmplot(x=x_field_label, y=y_field_label, 
                          data=chart_data, order=chart_order, 
                          color=swarmplot_colour, size=7, ax=ax1);
    
    #plt.setp(ax1.get_xticklabels(), rotation=90);
    if y_max_lim > 0:
        plt.ylim(0, y_max_lim)
    else:
        plt.ylim(0);
    ax1.set_ylabel(y_field_label);
    ax1.set_xlabel(x_field_label);
    ax1.set_title(chart_title);
    
    wrap_labels(ax1)
    
def plot_n_towns(top=True, property_type=None, num_towns=10, plot_swarm=True, y_max_lim=0):
    '''
    Plots a boxplot, with optional swarm plots, for the top or bottom num_towns based on 
    mean and median price for either all properties or a specific property.
    
    top: True (default) | False - If set to false plot bottom towns.
    
    property_type: By default set to None, which means compute mean and median for all 
                   properties within each town. A specific property type can be any one
                   of:- apartment, penthouse, maisonette, town house, terraced house, 
                        villa, bungalow.
                        
    num_towns: Number of towns to include, by default set to 10.
    '''
    
    if property_type is None:
        filtered_property = property_Q4
    else:
        property_type_list = ['apartment','penthouse','maisonette','town house',
                              'terraced house','villa','bungalow']
        if property_type not in property_type_list:
            print "property_type must be one of {0}.".format(property_type_list)
            return
        filtered_property = property_Q4[property_Q4['property_type'] == property_type]
    filtered_grouped_property = filtered_property.groupby(['location'], as_index=False)

    location_mean = filtered_grouped_property['price'].mean().sort_values(by='price', ascending=False)
    location_mean = location_mean.head(n=num_towns) if top else location_mean.tail(n=num_towns)
    location_mean = list(location_mean['location'])
    location_mean_filtered_data = filtered_property[filtered_property['location'].isin(location_mean)]
    location_mean_filtered_data = location_mean_filtered_data.rename(columns = {'location':'Location', 'price':'Price (Euro)'})

    location_median = filtered_grouped_property['price'].median().sort_values(by='price', ascending=False)
    location_median = location_median.head(n=num_towns) if top else location_median.tail(n=num_towns)
    location_median = list(location_median['location'])
    location_median_filtered_data = filtered_property[filtered_property['location'].isin(location_median)]
    location_median_filtered_data = location_median_filtered_data.rename(columns = {'location':'Location', 'price':'Price (Euro)'})

    if property_type is None:
        chart_title_mean = '{0} {1} {2} Localities by Mean Price Q4 2016'.format('Top' if top else 'Bottom', num_towns, 'Most Expensive' if top else 'Least Expensive')
    else:
        chart_title_mean = '{0} {1} {2} Localities for {3}s by Mean Price Q4 2016'.format('Top' if top else 'Bottom', num_towns, 'Most Expensive' if top else 'Least Expensive', property_type.title())
    
    boxplot_chart_helper('Location', 'Price (Euro)', chart_title_mean, location_mean_filtered_data,
                         location_mean, plot_swarm, y_max_lim, top, num_towns);
    
    if property_type is None:
        chart_title_median = '{0} {1} {2} Localities by Median Price Q4 2016'.format('Top' if top else 'Bottom', num_towns, 'Most Expensive' if top else 'Least Expensive')
    else:
        chart_title_median = '{0} {1} {2} Localities for {3}s by Median Price Q4 2016'.format('Top' if top else 'Bottom', num_towns, 'Most Expensive' if top else 'Least Expensive', property_type.title())

    boxplot_chart_helper('Location', 'Price (Euro)', chart_title_median, location_median_filtered_data,
                         location_median, plot_swarm, y_max_lim, top, num_towns);

Most Expensive Localities as of Q4 2016

In this section we list the overall most expensive localities using the mean and median prices, and then provide a breakdown by property type.

Most Expensive Localities Overall as of Q4 2016

plot_n_towns()

png

png

Most Expensive Localities for Apartments as of Q4 2016

plot_n_towns(property_type='apartment')

png

png

Most Expensive Localities for Penthouses as of Q4 2016

plot_n_towns(property_type='penthouse')

png

png

Most Expensive Localities for Maisonettes as of Q4 2016

plot_n_towns(property_type='maisonette')

png

png

Most Expensive Localities for Town Houses as of Q4 2016

plot_n_towns(property_type='town house', y_max_lim=5000000)

png

png

Most Expensive Localities for Terraced Houses as of Q4 2016

plot_n_towns(property_type='terraced house')

png

png

Most Expensive Localities for Villas as of Q4 2016

plot_n_towns(property_type='villa')

png

png

Most Expensive Localities for Bungalows as of Q4 2016

plot_n_towns(property_type='bungalow', y_max_lim=4000000)

png

png

Least Expensive Localities as of Q4 2016

In this section we list the overall least expensive localities using the mean and median prices, and then provide a breakdown by property type.

Least Expensive Localities Overall as of Q4 2016

plot_n_towns(top=False, y_max_lim=600000)

png

png

Least Expensive Localities for Apartments as of Q4 2016

plot_n_towns(top=False, property_type='apartment', y_max_lim=300000)

png

png

Least Expensive Localities for Penthouses as of Q4 2016

plot_n_towns(top=False, property_type='penthouse', y_max_lim=500000)

png

png

Least Expensive Localities for Maisonettes as of Q4 2016

plot_n_towns(top=False, property_type='maisonette')

png

png

Least Expensive Localities for Town Houses as of Q4 2016

plot_n_towns(top=False, property_type='town house', y_max_lim=600000)

png

png

Least Expensive Localities for Terraced Houses as of Q4 2016

plot_n_towns(top=False, property_type='terraced house')

png

png

Least Expensive Localities for Villas as of Q4 2016

plot_n_towns(top=False, property_type='villa')

png

png

Least Expensive Localities for Bungalows as of Q4 2016

plot_n_towns(top=False, property_type='bungalow', y_max_lim=4000000)

png

png


Tables

Price Changes per Property Type - Q4 vs Q2 2016

View Charts

plot_mmppc_by_pt(display_charts=False, display_tables=True)
Mean price percentage change per property type
Property Type Absolute Change (Euro) Percentage Change
5 town house 55,209 16.14
0 apartment 35,557 15.63
1 bungalow 249,702 14.69
3 penthouse 38,458 11.21
2 maisonette 15,675 8.47
4 terraced house 6,141 1.80
6 villa -15,269 -1.15
Median price percentage change per property type
Property Type Absolute Change (Euro) Percentage Change
1 bungalow 305,000 21.79
5 town house 35,000 16.67
3 penthouse 24,825 12.60
0 apartment 13,000 8.84
2 maisonette 10,000 6.25
6 villa 25,000 2.63
4 terraced house 5,596 1.75

Apartment Price Changes per Town - Q4 vs Q2 2016

View charts

plot_mmppc_by_ptat('apartment', display_charts=False, display_tables=True)
apartment - Top 5 locations - Largest mean price percentage change
Location Mean Q2 2016 Mean Q4 2016 Absolute Change (Euro) Percentage Change
31 Cospicua 104,817 226,083 121,266 115.69
191 Senglea 206,000 319,400 113,400 55.05
150 Pieta 180,141 269,473 89,332 49.59
121 Mosta 193,147 265,350 72,203 37.38
205 St. Paul'S Bay 216,010 293,495 77,485 35.87
apartment - Bottom 5 locations - Smallest mean price percentage change
Location Mean Q2 2016 Mean Q4 2016 Absolute Change (Euro) Percentage Change
49 Gharb 128,162 119,912 -8,250 -6.44
119 Mgarr (Gozo) 387,500 353,666 -33,834 -8.73
258 Zebbug (Gozo) 178,780 153,916 -24,864 -13.91
62 Gzira 286,655 240,131 -46,524 -16.23
223 Valletta 620,600 463,142 -157,458 -25.37
apartment - Top 5 locations - Largest median price percentage change
Location Median Q2 2016 Median Q4 2016 Absolute Change (Euro) Percentage Change
213 Ta' Xbiex 425,000 950,000 525,000 123.53
31 Cospicua 102,500 185,250 82,750 80.73
191 Senglea 210,000 350,000 140,000 66.67
167 Rabat 143,000 206,750 63,750 44.58
150 Pieta 136,260 186,000 49,740 36.50
apartment - Bottom 5 locations - Smallest median price percentage change
Location Median Q2 2016 Median Q4 2016 Absolute Change (Euro) Percentage Change
9 Bahrija 186,000 170,000 -16,000 -8.60
119 Mgarr (Gozo) 419,500 350,000 -69,500 -16.57
134 Nadur 173,000 135,000 -38,000 -21.97
77 Kercem 123,000 90,000 -33,000 -26.83
223 Valletta 580,000 285,000 -295,000 -50.86

Penthouse Price Changes per Town - Q4 vs Q2 2016

View charts

plot_mmppc_by_ptat('penthouse', display_charts=False, display_tables=True)
penthouse - Top 5 locations - Largest mean price percentage change
Location Mean Q2 2016 Mean Q4 2016 Absolute Change (Euro) Percentage Change
124 Mosta 214,750 349,093 134,343 62.56
3 Attard 217,500 320,600 103,100 47.40
29 Bugibba 267,825 377,945 110,120 41.12
71 Ibrag 421,400 573,625 152,225 36.12
207 St. Paul'S Bay 265,096 344,898 79,802 30.10
penthouse - Bottom 5 locations - Smallest mean price percentage change
Location Mean Q2 2016 Mean Q4 2016 Absolute Change (Euro) Percentage Change
183 Sannat 147,337 130,520 -16,817 -11.41
198 Sliema 933,742 817,020 -116,722 -12.50
152 Portomaso 2,712,500 2,367,500 -345,000 -12.72
64 Gzira 672,562 573,428 -99,134 -14.74
98 Marsalforn 176,645 135,737 -40,908 -23.16
penthouse - Top 5 locations - Largest median price percentage change
Location Median Q2 2016 Median Q4 2016 Absolute Change (Euro) Percentage Change
29 Bugibba 150,000 273,250 123,250 82.17
168 Rabat 241,500 410,000 168,500 69.77
54 Gharghur 320,000 425,000 105,000 32.81
124 Mosta 218,500 282,500 64,000 29.29
3 Attard 211,000 272,500 61,500 29.15
penthouse - Bottom 5 locations - Smallest median price percentage change
Location Median Q2 2016 Median Q4 2016 Absolute Change (Euro) Percentage Change
159 Qawra 350,000 277,900 -72,100 -20.60
136 Nadur 244,586 194,000 -50,586 -20.68
152 Portomaso 3,125,000 2,435,000 -690,000 -22.08
183 Sannat 151,400 110,000 -41,400 -27.34
98 Marsalforn 178,000 127,500 -50,500 -28.37

Maisonette Price Changes per Town - Q4 vs Q2 2016

View charts

plot_mmppc_by_ptat('maisonette', display_charts=False, display_tables=True)
maisonette - Top 5 locations - Largest mean price percentage change
Location Mean Q2 2016 Mean Q4 2016 Absolute Change (Euro) Percentage Change
175 San Gwann 220,413 347,833 127,420 57.81
187 Santa Venera 136,943 200,291 63,348 46.26
237 Xemxija 217,400 317,285 99,885 45.95
32 Cospicua 120,871 176,300 55,429 45.86
140 Naxxar 219,800 293,485 73,685 33.52
maisonette - Bottom 5 locations - Smallest mean price percentage change
Location Mean Q2 2016 Mean Q4 2016 Absolute Change (Euro) Percentage Change
84 Luqa 122,251 112,202 -10,049 -8.22
226 Victoria 123,500 111,722 -11,778 -9.54
130 Msida 167,428 148,000 -19,428 -11.60
267 Zurrieq 174,457 154,106 -20,351 -11.67
206 St. Paul'S Bay 149,172 126,775 -22,397 -15.01
maisonette - Top 5 locations - Largest median price percentage change
Location Median Q2 2016 Median Q4 2016 Absolute Change (Euro) Percentage Change
237 Xemxija 160,000 350,000 190,000 118.75
175 San Gwann 180,000 366,000 186,000 103.33
232 Xaghra 123,000 219,500 96,500 78.46
32 Cospicua 101,000 169,950 68,950 68.27
140 Naxxar 193,000 295,000 102,000 52.85
maisonette - Bottom 5 locations - Smallest median price percentage change
Location Median Q2 2016 Median Q4 2016 Absolute Change (Euro) Percentage Change
12 Balzan 280,000 258,000 -22,000 -7.86
267 Zurrieq 162,250 138,000 -24,250 -14.95
208 Swatar 200,000 170,000 -30,000 -15.00
63 Gzira 306,000 252,500 -53,500 -17.48
206 St. Paul'S Bay 150,000 121,154 -28,846 -19.23

Town House Price Changes per Town - Q4 vs Q2 2016

View charts

plot_mmppc_by_ptat('town house', display_charts=False, display_tables=True)
town house - Top 5 locations - Largest mean price percentage change
Location Mean Q2 2016 Mean Q4 2016 Absolute Change (Euro) Percentage Change
142 Naxxar 281,625 473,000 191,375 67.95
33 Cospicua 202,390 336,083 133,693 66.06
204 St. Julians 1,239,300 1,996,666 757,366 61.11
218 Tarxien 204,088 289,320 85,232 41.76
20 Birkirkara 286,316 386,638 100,322 35.04
town house - Bottom 5 locations - Smallest mean price percentage change
Location Mean Q2 2016 Mean Q4 2016 Absolute Change (Euro) Percentage Change
87 Luqa 193,526 189,442 -4,084 -2.11
138 Nadur 301,375 293,642 -7,733 -2.57
229 Victoria 280,984 261,470 -19,514 -6.94
95 Marsa 136,888 123,666 -13,222 -9.66
195 Siggiewi 432,900 370,400 -62,500 -14.44
town house - Top 5 locations - Largest median price percentage change
Location Median Q2 2016 Median Q4 2016 Absolute Change (Euro) Percentage Change
204 St. Julians 900,000 2,400,000 1,500,000 166.67
128 Mqabba 161,500 375,000 213,500 132.20
142 Naxxar 258,000 375,000 117,000 45.35
257 Zebbug 195,500 271,214 75,714 38.73
190 Santa Venera 184,500 248,569 64,069 34.73
town house - Bottom 5 locations - Smallest median price percentage change
Location Median Q2 2016 Median Q4 2016 Absolute Change (Euro) Percentage Change
138 Nadur 265,000 235,000 -30,000 -11.32
26 Birzebbugia 280,000 246,000 -34,000 -12.14
270 Zurrieq 345,750 290,000 -55,750 -16.12
199 Sliema 975,000 790,000 -185,000 -18.97
229 Victoria 256,230 200,000 -56,230 -21.95

Terraced House Price Changes per Town - Q4 vs Q2 2016

View charts

plot_mmppc_by_ptat('terraced house', display_charts=False, display_tables=True)
terraced house - Top 5 locations - Largest mean price percentage change
Location Mean Q2 2016 Mean Q4 2016 Absolute Change (Euro) Percentage Change
86 Luqa 289,611 358,312 68,701 23.72
19 Birkirkara 346,800 428,916 82,116 23.68
38 Fgura 269,121 312,500 43,379 16.12
169 Rabat 481,625 554,300 72,675 15.09
189 Santa Venera 224,040 256,733 32,693 14.59
terraced house - Bottom 5 locations - Smallest mean price percentage change
Location Mean Q2 2016 Mean Q4 2016 Absolute Change (Euro) Percentage Change
25 Birzebbugia 350,214 339,375 -10,839 -3.09
67 Hamrun 296,666 285,000 -11,666 -3.93
125 Mosta 494,933 462,625 -32,308 -6.53
147 Paola 322,875 299,500 -23,375 -7.24
240 Xewkija 396,000 333,166 -62,834 -15.87
terraced house - Top 5 locations - Largest median price percentage change
Location Median Q2 2016 Median Q4 2016 Absolute Change (Euro) Percentage Change
103 Marsascala 234,250 370,000 135,750 57.95
169 Rabat 492,500 687,000 194,500 39.49
107 Marsaxlokk 287,500 372,150 84,650 29.44
38 Fgura 241,100 299,500 58,400 24.22
264 Zejtun 297,000 365,000 68,000 22.90
terraced house - Bottom 5 locations - Smallest median price percentage change
Location Median Q2 2016 Median Q4 2016 Absolute Change (Euro) Percentage Change
147 Paola 285,250 273,000 -12,250 -4.29
67 Hamrun 297,000 273,500 -23,500 -7.91
125 Mosta 507,800 459,000 -48,800 -9.61
114 Mellieha 462,000 398,000 -64,000 -13.85
19 Birkirkara 425,000 322,500 -102,500 -24.12

Villa Price Changes per Town - Q4 vs Q2 2016

View charts

plot_mmppc_by_ptat('villa', display_charts=False, display_tables=True)
villa - Top 5 locations - Largest mean price percentage change
Location Mean Q2 2016 Mean Q4 2016 Absolute Change (Euro) Percentage Change
15 Birguma 1,171,666 1,700,000 528,334 45.09
116 Mellieha 1,192,857 1,427,142 234,285 19.64
8 Bahar Ic-Caghaq 1,463,222 1,707,142 243,920 16.67
127 Mosta 729,800 809,750 79,950 10.96
90 Madliena 3,155,000 3,476,363 321,363 10.19
villa - Bottom 5 locations - Smallest mean price percentage change
Location Mean Q2 2016 Mean Q4 2016 Absolute Change (Euro) Percentage Change
108 Marsaxlokk 807,333 701,800 -105,533 -13.07
246 Xlendi 576,400 494,428 -81,972 -14.22
5 Attard 1,040,562 867,548 -173,014 -16.63
14 Balzan 1,466,250 1,203,750 -262,500 -17.90
212 Swieqi 1,452,000 719,333 -732,667 -50.46
villa - Top 5 locations - Largest median price percentage change
Location Median Q2 2016 Median Q4 2016 Absolute Change (Euro) Percentage Change
15 Birguma 1,200,000 1,500,000 300,000 25.00
90 Madliena 2,400,000 3,000,000 600,000 25.00
116 Mellieha 1,050,000 1,290,000 240,000 22.86
235 Xaghra 1,003,523 1,200,000 196,477 19.58
127 Mosta 550,000 649,500 99,500 18.09
villa - Bottom 5 locations - Smallest median price percentage change
Location Median Q2 2016 Median Q4 2016 Absolute Change (Euro) Percentage Change
5 Attard 797,500 770,500 -27,000 -3.39
246 Xlendi 540,000 499,000 -41,000 -7.59
212 Swieqi 815,000 750,000 -65,000 -7.98
108 Marsaxlokk 769,000 688,000 -81,000 -10.53
14 Balzan 1,500,000 975,000 -525,000 -35.00

Bungalow Price Changes per Town - Q4 vs Q2 2016

View charts

plot_mmppc_by_ptat('bungalow', display_charts=False, display_tables=True)
bungalow - Top 5 locations - Largest mean price percentage change
Location Mean Q2 2016 Mean Q4 2016 Absolute Change (Euro) Percentage Change
122 Mosta 1,938,250 2,411,000 472,750 24.39
72 Iklin 1,922,500 2,203,333 280,833 14.61
248 Zabbar 721,612 813,650 92,038 12.75
180 San Pawl Tat-Targa 1,907,500 1,995,000 87,500 4.59
100 Marsascala 2,267,500 2,290,714 23,214 1.02
bungalow - Bottom 5 locations - Smallest mean price percentage change
Location Mean Q2 2016 Mean Q4 2016 Absolute Change (Euro) Percentage Change
248 Zabbar 721,612 813,650 92,038 12.75
180 San Pawl Tat-Targa 1,907,500 1,995,000 87,500 4.59
100 Marsascala 2,267,500 2,290,714 23,214 1.02
1 Attard 978,800 959,000 -19,800 -2.02
111 Mellieha 2,222,300 2,137,928 -84,372 -3.80
bungalow - Top 5 locations - Largest median price percentage change
Location Median Q2 2016 Median Q4 2016 Absolute Change (Euro) Percentage Change
122 Mosta 1,866,500 2,780,000 913,500 48.94
72 Iklin 1,745,000 2,200,000 455,000 26.07
180 San Pawl Tat-Targa 1,690,000 1,845,000 155,000 9.17
248 Zabbar 670,475 690,950 20,475 3.05
1 Attard 870,100 825,500 -44,600 -5.13
bungalow - Bottom 5 locations - Smallest median price percentage change
Location Median Q2 2016 Median Q4 2016 Absolute Change (Euro) Percentage Change
180 San Pawl Tat-Targa 1,690,000 1,845,000 155,000 9.17
248 Zabbar 670,475 690,950 20,475 3.05
1 Attard 870,100 825,500 -44,600 -5.13
111 Mellieha 1,650,000 1,550,000 -100,000 -6.06
100 Marsascala 2,450,000 2,000,000 -450,000 -18.37