Malta Property to Let - Q1 2017 vs Q1 2018 In-Depth Analysis

40 minute read

In this report we will analyse property for rent in Malta to compute and visualise statistics such as average prices, by property type, town and region. Two datasets collected a year apart, in Q1 2017 and Q1 2018, are compared to analyse rent price trends.

Data Collection

The property for rent 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 a year apart, one in Q1 2017 and another in Q1 2018.

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

from IPython.display import set_matplotlib_formats

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: '{:,.2f}'.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);
    
datasets_path = '/home/jovyan/work/machine learning/ds/projects/property-analysis/webapp/pa-api/v1.0/data/malta/mt01/rent/'
property_set1_title = 'Q1 2017'
property_set1_id = '2017Q1'
property_set1_location = '20170314141115/clean_data.csv'
property_set2_title = 'Q1 2018'
property_set2_id = '2018Q1'
property_set2_location = '20180224140148/clean_data.csv'

property_set1 = pandas.read_csv(datasets_path + property_set1_location)
property_set2 = pandas.read_csv(datasets_path + property_set2_location)

# title case locations
property_set1['location'] = property_set1['location'].str.title()
property_set2['location'] = property_set2['location'].str.title()

num_properties_in_set1 = len(property_set1)
num_properties_in_set2 = len(property_set2)
difference_in_properties = num_properties_in_set1 - num_properties_in_set2

print "{0}: {1} properties".format(property_set1_title, num_properties_in_set1)
print "{0}: {1} properties".format(property_set2_title, 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)
Q1 2017: 11171 properties
Q1 2018: 12039 properties
Latest data set has 868 properties more than the previous data set - (7.2%).
property_set1['dataset_id'] = property_set1_id
property_set2['dataset_id'] = property_set2_id
both_property_sets = pandas.concat([property_set1, property_set2])

f, (ax1) = plt.subplots(1, 1, figsize=(14, 6))
both_property_sets.sort_values(by=['dataset_id','property_type'], inplace=True)
seaborn.countplot(x='property_type', hue='dataset_id', data=both_property_sets, palette="Greens_d", ax=ax1);
ax1.set_xlabel('Property Type');
ax1.set_ylabel('Property Count');
ax1.legend(title="Property in")
ax1.set_title('Distribution of Property Types for Rent in Malta - {0} vs {1}'.format(property_set1_title, property_set2_title));
wrap_labels(ax1)

png

Notes

  • We have no way to validate the prices at which properties are listed for rent. These can vary from the actual monthly rent 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 rental 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_set2.groupby('location').count().sort_values(by='price', ascending=False).index.values
seaborn.countplot(x="location", data=property_set2, order=location_order, palette="Greens_d", ax=ax1);
ax1.set_xlabel('Location');
ax1.set_ylabel('Property Count');
ax1.set_title('Distribution of Property for Rent Across Malta - {0}'.format(property_set2_title));
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_set2[property_set2['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 Rent - {2}'.format(num_towns, property_type.title(), property_set2_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 rental property market.

f, (ax1) = plt.subplots(1, 1, figsize=(14,6))
both_property_sets.sort_values(by=['dataset_id','property_type'], inplace=True)
seaborn.set_style('whitegrid');
seaborn.boxplot(x="property_type", y="price", hue="dataset_id", data=both_property_sets, palette="Greens_d", ax=ax1);
ax1.set_yscale('log');
ax1.set_xlabel('Property Type');
ax1.set_ylabel('Price in Euro (Log Scale)');
ax1.legend(title="Property in")
ax1.set_title('Malta Property Rental Price Distribution by Property Type - {0} vs {1}'.format(property_set1_title, property_set2_title));
wrap_labels(ax1)

png

To quantify any rental price movements 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.

set2_mean_prices_per_property_type = property_set2.groupby('property_type', as_index=False)['price'].mean()
set1_mean_prices_per_property_type = property_set1.groupby('property_type', as_index=False)['price'].mean()
property_type_mean_price_difference = pandas.DataFrame(set2_mean_prices_per_property_type['property_type'])
property_type_mean_price_difference.columns = ['Property Type']
property_type_mean_price_difference['Absolute Change (Euro / month)'] = set2_mean_prices_per_property_type['price'] - set1_mean_prices_per_property_type['price']
property_type_mean_price_difference['Percentage Change'] = numpy.round((property_type_mean_price_difference['Absolute Change (Euro / month)'] / set1_mean_prices_per_property_type['price']) * 100, 2)

set2_median_prices_per_property_type = property_set2.groupby('property_type', as_index=False)['price'].median()
set1_median_prices_per_property_type = property_set1.groupby('property_type', as_index=False)['price'].median()
property_type_median_price_difference = pandas.DataFrame(set2_median_prices_per_property_type['property_type'])
property_type_median_price_difference.columns = ['Property Type']
property_type_median_price_difference['Absolute Change (Euro / month)'] = set2_median_prices_per_property_type['price'] - set1_median_prices_per_property_type['price']
property_type_median_price_difference['Percentage Change'] = numpy.round((property_type_median_price_difference['Absolute Change (Euro / month)'] / set1_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 Rental Property - Price Changes per Property Type - {0} vs {1}'.format(property_set1_title, property_set2_title), 
                     fontsize=20, verticalalignment='bottom');
    
    # mean price percentage change    
    
    if display_charts:
        property_type_mean_price_difference.sort_values(by='Absolute Change (Euro / month)', ascending=False, inplace=True)
        plot_chart_helper('Property Type', 'Absolute Change (Euro / month)', '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 / month)':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 / month)', ascending=False, inplace=True)
        plot_chart_helper('Property Type', 'Absolute Change (Euro / month)', '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 / month)':lambda_format_thousands}, classes='right_aligned hide_side_header')))

Rental Price Changes per Property Type - Q1 2017 vs Q1 2018

View Tables

plot_mmppc_by_pt()

png

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.

set2_mean_prices_per_location_property = property_set2.groupby(['location','property_type'], as_index=False)['price'].mean()
set1_mean_prices_per_location_property = property_set1.groupby(['location','property_type'], as_index=False)['price'].mean()
mean_results = set1_mean_prices_per_location_property.merge(set2_mean_prices_per_location_property, on=['location','property_type'])
mean_results = mean_results.rename(columns = {'location':'Location', 'property_type':'Property Type', 'price_x':'Mean '+property_set1_title, 'price_y':'Mean '+property_set2_title})
mean_results['Absolute Change (Euro / month)'] = mean_results['Mean '+property_set2_title] - mean_results['Mean '+property_set1_title]
mean_results['Percentage Change'] = numpy.round((mean_results['Absolute Change (Euro / month)'] / mean_results['Mean '+property_set1_title]) * 100,2)

set2_median_prices_per_location_property = property_set2.groupby(['location','property_type'], as_index=False)['price'].median()
set1_median_prices_per_location_property = property_set1.groupby(['location','property_type'], as_index=False)['price'].median()
median_results = set1_median_prices_per_location_property.merge(set2_median_prices_per_location_property, on=['location','property_type'])
median_results = median_results.rename(columns = {'location':'Location', 'property_type':'Property Type', 'price_x':'Median '+property_set1_title, 'price_y':'Median '+property_set2_title})
median_results['Absolute Change (Euro / month)'] = median_results['Median '+property_set2_title] - median_results['Median '+property_set1_title]
median_results['Percentage Change'] = numpy.round((median_results['Absolute Change (Euro / month)'] / median_results['Median '+property_set1_title]) * 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 Rental Property - {0} Price Changes per Town - {1} vs {2})'.format(property_type.title(), property_set1_title, property_set2_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 / month)':lambda_format_thousands,
                                                                              'Mean '+property_set1_title:lambda_format_thousands,
                                                                              'Mean '+property_set2_title: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 / month)':lambda_format_thousands,
                                                                              'Mean '+property_set1_title:lambda_format_thousands,
                                                                              'Mean '+property_set2_title: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 / month)':lambda_format_thousands,
                                                                                'Median '+property_set1_title:lambda_format_thousands,
                                                                                'Median '+property_set2_title: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 / month)':lambda_format_thousands,
                                                                                'Median '+property_set1_title:lambda_format_thousands,
                                                                                'Median '+property_set2_title:lambda_format_thousands},
                                                                    classes='right_aligned hide_side_header')))

Apartment Price Changes per Town - Q1 2017 vs Q1 2018

View tables

plot_mmppc_by_ptat('apartment')

png

Penthouse Price Changes per Town - Q1 2017 vs Q1 2018

View tables

plot_mmppc_by_ptat('penthouse')

png

Maisonette Price Changes per Town - Q1 2017 vs Q1 2018

View tables

plot_mmppc_by_ptat('maisonette')

png

Town House Price Changes per Town - Q1 2017 vs Q1 2018

View tables

plot_mmppc_by_ptat('town house')

png

Terraced House Price Changes per Town - Q1 2017 vs Q1 2018

View tables

plot_mmppc_by_ptat('terraced house')

png

Villa Price Changes per Town - Q1 2017 vs Q1 2018

View tables

plot_mmppc_by_ptat('villa')

png

Bungalow Price Changes per Town - Q1 2017 vs Q1 2018

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_set2
    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_set2[property_set2['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 / month)'})

    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 / month)'})

    if property_type is None:
        chart_title_mean = '{0} {1} {2} Localities by Mean Rental Price {3}'.format('Top' if top else 'Bottom', num_towns, 'Most Expensive' if top else 'Least Expensive', property_set2_title)
    else:
        chart_title_mean = '{0} {1} {2} Localities for {3}s by Mean Rental Price {4}'.format('Top' if top else 'Bottom', num_towns, 'Most Expensive' if top else 'Least Expensive', property_type.title(), property_set2_title)
    
    boxplot_chart_helper('Location', 'Price (Euro / month)', 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 Rental Price {3}'.format('Top' if top else 'Bottom', num_towns, 'Most Expensive' if top else 'Least Expensive', property_set2_title)
    else:
        chart_title_median = '{0} {1} {2} Localities for {3}s by Median Rental Price {4}'.format('Top' if top else 'Bottom', num_towns, 'Most Expensive' if top else 'Least Expensive', property_type.title(), property_set2_title)

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

Most Expensive Localities as of Q1 2018

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 Q1 2018

plot_n_towns()

png

png

Most Expensive Localities for Apartments as of Q1 2018

plot_n_towns(property_type='apartment')

png

png

Most Expensive Localities for Penthouses as of Q1 2018

plot_n_towns(property_type='penthouse')

png

png

Most Expensive Localities for Maisonettes as of Q1 2018

plot_n_towns(property_type='maisonette')

png

png

Most Expensive Localities for Town Houses as of Q1 2018

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

png

png

Most Expensive Localities for Terraced Houses as of Q1 2018

plot_n_towns(property_type='terraced house', y_max_lim=5000)

png

png

Most Expensive Localities for Villas as of Q1 2018

plot_n_towns(property_type='villa')

png

png

Most Expensive Localities for Bungalows as of Q1 2018

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

png

png

Least Expensive Localities as of Q1 2018

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 Q1 2018

plot_n_towns(top=False, y_max_lim=1500)

png

png

Least Expensive Localities for Apartments as of Q1 2018

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

png

png

Least Expensive Localities for Penthouses as of Q1 2018

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

png

png

Least Expensive Localities for Maisonettes as of Q1 2018

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

png

png

Least Expensive Localities for Town Houses as of Q1 2018

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

png

png

Least Expensive Localities for Terraced Houses as of Q1 2018

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

png

png

Least Expensive Localities for Villas as of Q1 2018

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

png

png

Least Expensive Localities for Bungalows as of Q1 2018

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

png

png


Tables

Price Changes per Property Type - Q1 2018 vs Q1 2017

View Charts

plot_mmppc_by_pt(display_charts=False, display_tables=True)
Mean price percentage change per property type
Property Type Absolute Change (Euro / month) Percentage Change
5 town house 296.66 20.15
4 terraced house 145.88 13.45
2 maisonette 78.24 9.11
0 apartment 81.93 7.79
3 penthouse 79.59 6.49
1 bungalow 90.27 2.76
6 villa 72.89 2.21
Median price percentage change per property type
Property Type Absolute Change (Euro / month) Percentage Change
5 town house 225.00 23.08
0 apartment 100.00 12.50
3 penthouse 100.00 11.11
4 terraced house 62.50 7.04
2 maisonette 50.00 6.67
1 bungalow 100.00 4.00
6 villa 100.00 3.70

Apartment Price Changes per Town - Q1 2018 vs Q1 2017

View charts

plot_mmppc_by_ptat('apartment', display_charts=False, display_tables=True)
apartment - Top 5 locations - Largest mean price percentage change
Location Mean Q1 2017 Mean Q1 2018 Absolute Change (Euro / month) Percentage Change
189 San Lawrenz 1,092.00 1,608.33 516.33 47.28
137 Mtarfa 533.33 750.00 216.67 40.62
179 Safi 527.50 728.57 201.07 38.12
204 Siggiewi 654.55 847.22 192.68 29.44
88 Kercem 361.67 467.50 105.83 29.26
apartment - Bottom 5 locations - Smallest mean price percentage change
Location Mean Q1 2017 Mean Q1 2018 Absolute Change (Euro / month) Percentage Change
155 Pembroke 1,041.67 1,028.57 -13.10 -1.26
71 Hal Saghtrija 2,020.00 1,971.43 -48.57 -2.40
101 Manikata 928.29 883.19 -45.11 -4.86
48 Fort Chambray 1,033.33 900.00 -133.33 -12.90
81 Kalkara 3,100.00 2,076.47 -1,023.53 -33.02
apartment - Top 5 locations - Largest median price percentage change
Location Median Q1 2017 Median Q1 2018 Absolute Change (Euro / month) Percentage Change
204 Siggiewi 600.00 875.00 275.00 45.83
138 Munxar 350.00 500.00 150.00 42.86
88 Kercem 350.00 475.00 125.00 35.71
179 Safi 500.00 650.00 150.00 30.00
172 Qormi 550.00 700.00 150.00 27.27
apartment - Bottom 5 locations - Smallest median price percentage change
Location Median Q1 2017 Median Q1 2018 Absolute Change (Euro / month) Percentage Change
101 Manikata 900.00 875.00 -25.00 -2.78
75 Ibrag 1,200.00 1,150.00 -50.00 -4.17
236 Ta' Xbiex 2,000.00 1,900.00 -100.00 -5.00
48 Fort Chambray 1,000.00 900.00 -100.00 -10.00
81 Kalkara 3,500.00 1,300.00 -2,200.00 -62.86

Penthouse Price Changes per Town - Q1 2018 vs Q1 2017

View charts

plot_mmppc_by_ptat('penthouse', display_charts=False, display_tables=True)
penthouse - Top 5 locations - Largest mean price percentage change
Location Mean Q1 2017 Mean Q1 2018 Absolute Change (Euro / month) Percentage Change
161 Pieta 800.00 1,340.00 540.00 67.50
267 Xemxija 1,125.00 1,783.33 658.33 58.52
151 Paceville 991.67 1,462.50 470.83 47.48
86 Kappara 1,028.75 1,416.67 387.92 37.71
166 Qala 458.50 582.00 123.50 26.94
penthouse - Bottom 5 locations - Smallest mean price percentage change
Location Mean Q1 2017 Mean Q1 2018 Absolute Change (Euro / month) Percentage Change
255 Victoria 472.73 468.18 -4.55 -0.96
263 Xaghra 642.86 631.25 -11.61 -1.81
159 Pender Gardens 3,250.00 3,125.00 -125.00 -3.85
9 Bahar Ic-Caghaq 1,300.00 1,237.50 -62.50 -4.81
15 Balzan 1,293.75 1,175.00 -118.75 -9.18
penthouse - Top 5 locations - Largest median price percentage change
Location Median Q1 2017 Median Q1 2018 Absolute Change (Euro / month) Percentage Change
151 Paceville 725.00 1,150.00 425.00 58.62
267 Xemxija 1,100.00 1,600.00 500.00 45.45
86 Kappara 825.00 1,100.00 275.00 33.33
105 Marsalforn 625.00 787.50 162.50 26.00
166 Qala 462.50 580.00 117.50 25.41
penthouse - Bottom 5 locations - Smallest median price percentage change
Location Median Q1 2017 Median Q1 2018 Absolute Change (Euro / month) Percentage Change
263 Xaghra 600.00 575.00 -25.00 -4.17
51 Ghajnsielem 472.50 450.00 -22.50 -4.76
123 Mgarr 1,500.00 1,400.00 -100.00 -6.67
15 Balzan 1,100.00 1,000.00 -100.00 -9.09
159 Pender Gardens 3,500.00 3,175.00 -325.00 -9.29

Maisonette Price Changes per Town - Q1 2018 vs Q1 2017

View charts

plot_mmppc_by_ptat('maisonette', display_charts=False, display_tables=True)
maisonette - Top 5 locations - Largest mean price percentage change
Location Mean Q1 2017 Mean Q1 2018 Absolute Change (Euro / month) Percentage Change
46 Fontana 363.75 583.33 219.58 60.37
165 Qala 499.00 736.36 237.36 47.57
104 Marsalforn 385.20 558.00 172.80 44.86
214 St. Andrews 883.83 1,213.00 329.17 37.24
254 Victoria 453.00 598.18 145.18 32.05
maisonette - Bottom 5 locations - Smallest mean price percentage change
Location Mean Q1 2017 Mean Q1 2018 Absolute Change (Euro / month) Percentage Change
50 Ghajnsielem 456.82 449.29 -7.53 -1.65
40 Cospicua 875.00 810.71 -64.29 -7.35
205 Siggiewi 583.33 537.50 -45.83 -7.86
175 Rabat 825.00 757.50 -67.50 -8.18
183 Salina 612.50 516.67 -95.83 -15.65
maisonette - Top 5 locations - Largest median price percentage change
Location Median Q1 2017 Median Q1 2018 Absolute Change (Euro / month) Percentage Change
104 Marsalforn 301.00 600.00 299.00 99.34
262 Xaghra 330.00 600.00 270.00 81.82
46 Fontana 340.00 500.00 160.00 47.06
292 Zurrieq 457.50 650.00 192.50 42.08
214 St. Andrews 875.00 1,200.00 325.00 37.14
maisonette - Bottom 5 locations - Smallest median price percentage change
Location Median Q1 2017 Median Q1 2018 Absolute Change (Euro / month) Percentage Change
156 Pembroke 900.00 900.00 0.00 0.00
175 Rabat 800.00 775.00 -25.00 -3.12
183 Salina 525.00 500.00 -25.00 -4.76
30 Birzebbugia 675.00 625.00 -50.00 -7.41
205 Siggiewi 600.00 550.00 -50.00 -8.33

Town House Price Changes per Town - Q1 2018 vs Q1 2017

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 Q1 2017 Mean Q1 2018 Absolute Change (Euro / month) Percentage Change
220 St. Julians 1,850.00 3,737.50 1,887.50 102.03
257 Victoria 486.67 625.00 138.33 28.42
92 Lija 916.67 1,175.00 258.33 28.18
27 Birkirkara 693.75 883.33 189.58 27.33
17 Balzan 1,450.00 1,812.50 362.50 25.00
town house - Bottom 5 locations - Smallest mean price percentage change
Location Mean Q1 2017 Mean Q1 2018 Absolute Change (Euro / month) Percentage Change
70 Gzira 740.00 740.00 0.00 0.00
252 Valletta 4,320.00 4,274.55 -45.45 -1.05
57 Gharb 680.00 558.33 -121.67 -17.89
41 Cospicua 1,500.00 1,215.00 -285.00 -19.00
272 Xewkija 828.57 410.00 -418.57 -50.52
town house - Top 5 locations - Largest median price percentage change
Location Median Q1 2017 Median Q1 2018 Absolute Change (Euro / month) Percentage Change
17 Balzan 900.00 1,375.00 475.00 52.78
257 Victoria 485.00 650.00 165.00 34.02
220 St. Julians 2,075.00 2,750.00 675.00 32.53
226 St. Paul'S Bay 750.00 925.00 175.00 23.33
259 Vittoriosa 850.00 1,025.00 175.00 20.59
town house - Bottom 5 locations - Smallest median price percentage change
Location Median Q1 2017 Median Q1 2018 Absolute Change (Euro / month) Percentage Change
281 Zabbar 600.00 600.00 0.00 0.00
57 Gharb 625.00 600.00 -25.00 -4.00
272 Xewkija 500.00 450.00 -50.00 -10.00
252 Valletta 1,800.00 1,600.00 -200.00 -11.11
41 Cospicua 1,500.00 925.00 -575.00 -38.33

Terraced House Price Changes per Town - Q1 2018 vs Q1 2017

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 Q1 2017 Mean Q1 2018 Absolute Change (Euro / month) Percentage Change
211 Sliema 950.00 1,568.75 618.75 65.13
256 Victoria 564.50 734.29 169.79 30.08
219 St. Julians 1,755.00 2,261.11 506.11 28.84
95 Luqa 707.50 883.33 175.83 24.85
176 Rabat 930.00 1,140.00 210.00 22.58
terraced house - Bottom 5 locations - Smallest mean price percentage change
Location Mean Q1 2017 Mean Q1 2018 Absolute Change (Euro / month) Percentage Change
225 St. Paul'S Bay 1,077.78 1,042.86 -34.92 -3.24
271 Xewkija 617.86 592.86 -25.00 -4.05
195 San Pawl Tat-Targa 1,233.33 1,166.67 -66.67 -5.41
187 San Gwann 1,741.67 1,621.43 -120.24 -6.90
288 Zebbug (Gozo) 733.33 647.50 -85.83 -11.70
terraced house - Top 5 locations - Largest median price percentage change
Location Median Q1 2017 Median Q1 2018 Absolute Change (Euro / month) Percentage Change
219 St. Julians 1,450.00 2,300.00 850.00 58.62
95 Luqa 675.00 1,000.00 325.00 48.15
143 Nadur 500.00 725.00 225.00 45.00
157 Pembroke 1,500.00 2,000.00 500.00 33.33
4 Attard 1,000.00 1,300.00 300.00 30.00
terraced house - Bottom 5 locations - Smallest median price percentage change
Location Median Q1 2017 Median Q1 2018 Absolute Change (Euro / month) Percentage Change
124 Mgarr 800.00 800.00 0.00 0.00
118 Mellieha 825.00 825.00 0.00 0.00
294 Zurrieq 1,000.00 1,000.00 0.00 0.00
187 San Gwann 1,650.00 1,500.00 -150.00 -9.09
288 Zebbug (Gozo) 750.00 620.00 -130.00 -17.33

Villa Price Changes per Town - Q1 2018 vs Q1 2017

View charts

plot_mmppc_by_ptat('villa', display_charts=False, display_tables=True)
villa - Top 5 locations - Largest mean price percentage change
Location Mean Q1 2017 Mean Q1 2018 Absolute Change (Euro / month) Percentage Change
171 Qawra 1,119.00 1,391.25 272.25 24.33
238 Ta' Xbiex 4,595.33 5,400.00 804.67 17.51
178 Rabat 1,908.33 2,141.67 233.33 12.23
221 St. Julians 2,265.38 2,540.62 275.24 12.15
196 San Pawl Tat-Targa 2,985.71 3,266.67 280.95 9.41
villa - Bottom 5 locations - Smallest mean price percentage change
Location Mean Q1 2017 Mean Q1 2018 Absolute Change (Euro / month) Percentage Change
5 Attard 2,850.00 2,758.33 -91.67 -3.22
22 Birguma 4,166.67 3,975.00 -191.67 -4.60
80 Iklin 6,300.00 6,000.00 -300.00 -4.76
18 Balzan 2,066.67 1,950.00 -116.67 -5.65
120 Mellieha 2,943.48 2,676.09 -267.39 -9.08
villa - Top 5 locations - Largest median price percentage change
Location Median Q1 2017 Median Q1 2018 Absolute Change (Euro / month) Percentage Change
171 Qawra 815.00 1,007.50 192.50 23.62
188 San Gwann 2,000.00 2,250.00 250.00 12.50
221 St. Julians 2,000.00 2,250.00 250.00 12.50
100 Madliena 4,000.00 4,400.00 400.00 10.00
61 Ghasri 1,250.00 1,370.00 120.00 9.60
villa - Bottom 5 locations - Smallest median price percentage change
Location Median Q1 2017 Median Q1 2018 Absolute Change (Euro / month) Percentage Change
178 Rabat 2,000.00 2,000.00 0.00 0.00
80 Iklin 6,000.00 5,750.00 -250.00 -4.17
120 Mellieha 3,000.00 2,700.00 -300.00 -10.00
243 Tal-Ibrag 3,400.00 3,000.00 -400.00 -11.76
22 Birguma 4,500.00 3,900.00 -600.00 -13.33

Bungalow Price Changes per Town - Q1 2018 vs Q1 2017

View charts

plot_mmppc_by_ptat('bungalow', display_charts=False, display_tables=True)
bungalow - Top 5 locations - Largest mean price percentage change
Location Mean Q1 2017 Mean Q1 2018 Absolute Change (Euro / month) Percentage Change
145 Naxxar 2,900.00 3,925.00 1,025.00 35.34
7 Bahar Ic-Caghaq 4,866.67 5,460.00 593.33 12.19
19 Bidnija 2,700.00 3,000.00 300.00 11.11
192 San Pawl Tat-Targa 4,300.00 4,625.00 325.00 7.56
115 Mellieha 3,602.86 3,804.68 201.82 5.60
bungalow - Bottom 5 locations - Smallest mean price percentage change
Location Mean Q1 2017 Mean Q1 2018 Absolute Change (Euro / month) Percentage Change
77 Iklin 3,250.00 3,250.00 0.00 0.0
269 Xewkija 830.00 830.00 0.00 0.0
97 Madliena 5,000.00 4,890.00 -110.00 -2.2
168 Qawra 1,580.00 1,520.00 -60.00 -3.8
29 Birzebbugia 2,025.00 1,883.33 -141.67 -7.0
bungalow - Top 5 locations - Largest median price percentage change
Location Median Q1 2017 Median Q1 2018 Absolute Change (Euro / month) Percentage Change
145 Naxxar 3,050.00 3,850.00 800.00 26.23
115 Mellieha 2,500.00 3,000.00 500.00 20.00
19 Bidnija 2,600.00 2,900.00 300.00 11.54
7 Bahar Ic-Caghaq 5,000.00 5,500.00 500.00 10.00
29 Birzebbugia 1,450.00 1,550.00 100.00 6.90
bungalow - Bottom 5 locations - Smallest median price percentage change
Location Median Q1 2017 Median Q1 2018 Absolute Change (Euro / month) Percentage Change
77 Iklin 2,750.00 2,750.00 0.00 0.00
269 Xewkija 815.00 815.00 0.00 0.00
1 Attard 2,000.00 1,900.00 -100.00 -5.00
97 Madliena 5,500.00 5,000.00 -500.00 -9.09
168 Qawra 1,800.00 1,500.00 -300.00 -16.67