Stefan Fiott

Malta Property to Let - Q4 2016 vs Q1 2017 In-depth Analysis

First published: 20 Mar 2017
Last updated: 20 Mar 2017

Introduction

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. Property price trends are also determined by comparing two datasets, collected four months apart.

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 four months apart, one in November 2016 and another in March 2017.

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

In [1]:
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);

property_set1 = pandas.read_csv('/home/stefan/hostshare/lab/machine learning/ds/projects/property-analysis/webapp/pa-api/v1.0/data/malta/mt01/rent/20161118223018/clean_data.csv')
property_set2 = pandas.read_csv('/home/stefan/hostshare/lab/machine learning/ds/projects/property-analysis/webapp/pa-api/v1.0/data/malta/mt01/rent/20170314141115/clean_data.csv')

# 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 "Q4 2016: {0} properties".format(num_properties_in_set1)
print "Q1 2017: {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)
Q4 2016: 11835 properties
Q1 2017: 11171 properties
Previous data set has 664 properties more than the latest data set - (5.6%).
In [2]:
property_set1['dataset_id'] = '2016Q4'
property_set2['dataset_id'] = '2017Q1'
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 - Q4 2016 vs Q1 2017');
wrap_labels(ax1)

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

In [3]:
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 - Q1 2017');
plt.setp(ax1.get_xticklabels(), rotation=90);

seaborn.set(font_scale=1.5);
In [4]:
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 - Q1 2017'.format(num_towns, property_type.title()));

Top Towns Based on Availability of a Particular Property Type

In [5]:
plot_top_availability_towns('apartment')
In [6]:
plot_top_availability_towns('penthouse')
In [7]:
plot_top_availability_towns('maisonette')
In [8]:
plot_top_availability_towns('town house')
In [9]:
plot_top_availability_towns('terraced house')
In [10]:
plot_top_availability_towns('villa')
In [11]:
plot_top_availability_towns('bungalow', num_towns=5)

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.

In [12]:
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 - Q4 2016 vs Q1 2017');
wrap_labels(ax1)

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.

In [13]:
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 - Q4 2016 vs Q1 2017', 
                     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 - Q4 2016 vs Q1 2017

View Tables

In [14]:
plot_mmppc_by_pt()

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.

In [15]:
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 Q4 2016', 'price_y':'Mean Q1 2017'})
mean_results['Absolute Change (Euro / month)'] = mean_results['Mean Q1 2017'] - mean_results['Mean Q4 2016']
mean_results['Percentage Change'] = numpy.round((mean_results['Absolute Change (Euro / month)'] / mean_results['Mean Q4 2016']) * 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 Q4 2016', 'price_y':'Median Q1 2017'})
median_results['Absolute Change (Euro / month)'] = median_results['Median Q1 2017'] - median_results['Median Q4 2016']
median_results['Percentage Change'] = numpy.round((median_results['Absolute Change (Euro / month)'] / median_results['Median Q4 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 Rental Property - {0} Price Changes per Town - Q4 2016 vs Q1 2017)'.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 10 locations - Largest mean price percentage change".format(property_type)
        display(HTML(sorted_property_type_mean.head(n=num_towns).to_html(formatters={'Absolute Change (Euro / month)':lambda_format_thousands,
                                                                              'Mean Q4 2016':lambda_format_thousands,
                                                                              'Mean Q1 2017':lambda_format_thousands},
                                                                  classes='right_aligned hide_side_header')))
        print
        print "{0} - Bottom 10 locations - Smallest mean price percentage change".format(property_type)
        display(HTML(sorted_property_type_mean.tail(n=num_towns).to_html(formatters={'Absolute Change (Euro / month)':lambda_format_thousands,
                                                                              'Mean Q4 2016':lambda_format_thousands,
                                                                              'Mean Q1 2017':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 10 locations - Largest median price percentage change".format(property_type)
        display(HTML(sorted_property_type_median.head(n=num_towns).to_html(formatters={'Absolute Change (Euro / month)':lambda_format_thousands,
                                                                                'Median Q4 2016':lambda_format_thousands,
                                                                                'Median Q1 2017':lambda_format_thousands},
                                                                    classes='right_aligned hide_side_header')))
        print
        print "{0} - Bottom 10 locations - Smallest median price percentage change".format(property_type)
        display(HTML(sorted_property_type_median.tail(n=num_towns).to_html(formatters={'Absolute Change (Euro / month)':lambda_format_thousands,
                                                                                'Median Q4 2016':lambda_format_thousands,
                                                                                'Median Q1 2017':lambda_format_thousands},
                                                                    classes='right_aligned hide_side_header')))

Apartment Price Changes per Town - Q4 2016 vs Q1 2017

View tables

In [16]:
plot_mmppc_by_ptat('apartment')

Penthouse Price Changes per Town - Q4 2016 vs Q1 2017

View tables

In [17]:
plot_mmppc_by_ptat('penthouse')

Maisonette Price Changes per Town - Q4 2016 vs Q1 2017

View tables

In [18]:
plot_mmppc_by_ptat('maisonette')

Town House Price Changes per Town - Q4 2016 vs Q1 2017

View tables

In [19]:
plot_mmppc_by_ptat('town house')

Terraced House Price Changes per Town - Q4 2016 vs Q1 2017

View tables

In [20]:
plot_mmppc_by_ptat('terraced house')

Villa Price Changes per Town - Q4 2016 vs Q1 2017

View tables

In [21]:
plot_mmppc_by_ptat('villa')

Bungalow Price Changes per Town - Q4 2016 vs Q1 2017

View tables

In [22]:
plot_mmppc_by_ptat('bungalow', hspace=.4)
In [23]:
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 Q1 2017'.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 Rental Price Q1 2017'.format('Top' if top else 'Bottom', num_towns, 'Most Expensive' if top else 'Least Expensive', property_type.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 Q1 2017'.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 Rental Price Q1 2017'.format('Top' if top else 'Bottom', num_towns, 'Most Expensive' if top else 'Least Expensive', property_type.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 2017

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 2017

In [24]:
plot_n_towns()

Most Expensive Localities for Apartments as of Q1 2017

In [25]:
plot_n_towns(property_type='apartment')

Most Expensive Localities for Penthouses as of Q1 2017

In [26]:
plot_n_towns(property_type='penthouse')

Most Expensive Localities for Maisonettes as of Q1 2017

In [27]:
plot_n_towns(property_type='maisonette')

Most Expensive Localities for Town Houses as of Q1 2017

In [28]:
plot_n_towns(property_type='town house', y_max_lim=6000)

Most Expensive Localities for Terraced Houses as of Q1 2017

In [29]:
plot_n_towns(property_type='terraced house', y_max_lim=5000)

Most Expensive Localities for Villas as of Q1 2017

In [30]:
plot_n_towns(property_type='villa')

Most Expensive Localities for Bungalows as of Q1 2017

In [31]:
plot_n_towns(property_type='bungalow', y_max_lim=10000)

Least Expensive Localities as of Q1 2017

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 2017

In [32]:
plot_n_towns(top=False, y_max_lim=1500)

Least Expensive Localities for Apartments as of Q1 2017

In [33]:
plot_n_towns(top=False, property_type='apartment', y_max_lim=1500)

Least Expensive Localities for Penthouses as of Q1 2017

In [34]:
plot_n_towns(top=False, property_type='penthouse', y_max_lim=1500)

Least Expensive Localities for Maisonettes as of Q1 2017

In [35]:
plot_n_towns(top=False, property_type='maisonette')

Least Expensive Localities for Town Houses as of Q1 2017

In [36]:
plot_n_towns(top=False, property_type='town house', y_max_lim=1200)

Least Expensive Localities for Terraced Houses as of Q1 2017

In [37]:
plot_n_towns(top=False, property_type='terraced house', y_max_lim=1500)

Least Expensive Localities for Villas as of Q1 2017

In [38]:
plot_n_towns(top=False, property_type='villa', y_max_lim=6000)

Least Expensive Localities for Bungalows as of Q1 2017

In [39]:
plot_n_towns(top=False, property_type='bungalow', y_max_lim=6000)

Tables

Price Changes per Property Type - Q1 2017 vs Q4 2016

View Charts

In [40]:
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
2 maisonette 69.97 8.87
0 apartment 67.46 6.85
3 penthouse 69.44 6.01
4 terraced house 40.37 3.87
5 town house 26.56 1.84
6 villa 46.95 1.45
1 bungalow 17.28 0.53
Median price percentage change per property type
Property Type Absolute Change (Euro / month) Percentage Change
5 town house 125.00 14.71
2 maisonette 50.00 7.14
0 apartment 50.00 6.67
4 terraced house 37.50 4.41
6 villa 100.00 3.85
1 bungalow 0.00 0.00
3 penthouse 0.00 0.00

Apartment Price Changes per Town - Q1 2017 vs Q4 2016

View charts

In [41]:
plot_mmppc_by_ptat('apartment', display_charts=False, display_tables=True)
apartment - Top 10 locations - Largest mean price percentage change
Location Mean Q4 2016 Mean Q1 2017 Absolute Change (Euro / month) Percentage Change
190 Salina 543.00 798.89 255.89 47.13
174 Qawra 542.24 656.53 114.29 21.08
279 Xghajra 526.73 632.14 105.41 20.01
33 Bugibba 537.72 644.10 106.38 19.78
28 Birzebbugia 531.44 633.82 102.38 19.26
apartment - Bottom 10 locations - Smallest mean price percentage change
Location Mean Q4 2016 Mean Q1 2017 Absolute Change (Euro / month) Percentage Change
136 Mqabba 616.67 600.00 -16.67 -2.70
150 Naxxar 861.32 812.06 -49.26 -5.72
47 Fort Chambray 1,133.33 1,033.33 -100.00 -8.82
197 San Lawrenz 1,233.75 1,092.00 -141.75 -11.49
221 St. Andrews 940.00 825.00 -115.00 -12.23
apartment - Top 10 locations - Largest median price percentage change
Location Median Q4 2016 Median Q1 2017 Absolute Change (Euro / month) Percentage Change
190 Salina 480.00 675.00 195.00 40.62
21 Birguma 900.00 1,200.00 300.00 33.33
28 Birzebbugia 500.00 650.00 150.00 30.00
107 Marsa 400.00 500.00 100.00 25.00
279 Xghajra 500.00 625.00 125.00 25.00
apartment - Bottom 10 locations - Smallest median price percentage change
Location Median Q4 2016 Median Q1 2017 Absolute Change (Euro / month) Percentage Change
165 Pender Gardens 1,600.00 1,500.00 -100.00 -6.25
75 Hamrun 700.00 650.00 -50.00 -7.14
156 Paceville 975.00 900.00 -75.00 -7.69
47 Fort Chambray 1,200.00 1,000.00 -200.00 -16.67
221 St. Andrews 1,000.00 800.00 -200.00 -20.00

Penthouse Price Changes per Town - Q1 2017 vs Q4 2016

View charts

In [42]:
plot_mmppc_by_ptat('penthouse', display_charts=False, display_tables=True)
penthouse - Top 10 locations - Largest mean price percentage change
Location Mean Q4 2016 Mean Q1 2017 Absolute Change (Euro / month) Percentage Change
25 Birkirkara 737.50 1,177.94 440.44 59.72
157 Paceville 770.00 991.67 221.67 28.79
118 Marsaxlokk 1,312.50 1,583.33 270.83 20.63
15 Balzan 1,087.50 1,293.75 206.25 18.97
31 Birzebbugia 601.30 710.50 109.20 18.16
penthouse - Bottom 10 locations - Smallest mean price percentage change
Location Mean Q4 2016 Mean Q1 2017 Absolute Change (Euro / month) Percentage Change
77 Hamrun 1,250.00 1,175.00 -75.00 -6.00
103 Madliena 2,357.14 2,183.33 -173.81 -7.37
106 Manikata 1,420.00 1,314.29 -105.71 -7.44
60 Gharghur 1,203.33 1,113.33 -90.00 -7.48
38 Burmarrad 658.33 550.00 -108.33 -16.46
penthouse - Top 10 locations - Largest median price percentage change
Location Median Q4 2016 Median Q1 2017 Absolute Change (Euro / month) Percentage Change
273 Xemxija 750.00 1,100.00 350.00 46.67
118 Marsaxlokk 1,375.00 2,000.00 625.00 45.45
170 Portomaso 3,500.00 4,500.00 1,000.00 28.57
113 Marsascala 700.00 850.00 150.00 21.43
157 Paceville 600.00 725.00 125.00 20.83
penthouse - Bottom 10 locations - Smallest median price percentage change
Location Median Q4 2016 Median Q1 2017 Absolute Change (Euro / month) Percentage Change
238 Swatar 850.00 800.00 -50.00 -5.88
106 Manikata 1,200.00 1,100.00 -100.00 -8.33
60 Gharghur 1,000.00 900.00 -100.00 -10.00
77 Hamrun 1,250.00 1,100.00 -150.00 -12.00
103 Madliena 2,500.00 1,900.00 -600.00 -24.00

Maisonette Price Changes per Town - Q1 2017 vs Q4 2016

View charts

In [43]:
plot_mmppc_by_ptat('maisonette', display_charts=False, display_tables=True)
maisonette - Top 10 locations - Largest mean price percentage change
Location Mean Q4 2016 Mean Q1 2017 Absolute Change (Euro / month) Percentage Change
299 Zejtun 482.50 687.50 205.00 42.49
8 Bahar Ic-Caghaq 729.50 1,014.29 284.79 39.04
30 Birzebbugia 512.86 664.17 151.31 29.50
254 Valletta 837.50 1,041.67 204.17 24.38
117 Marsaxlokk 560.00 690.00 130.00 23.21
maisonette - Bottom 10 locations - Smallest mean price percentage change
Location Mean Q4 2016 Mean Q1 2017 Absolute Change (Euro / month) Percentage Change
59 Gharghur 882.76 873.44 -9.33 -1.06
276 Xewkija 500.00 491.67 -8.33 -1.67
138 Mriehel 600.00 583.33 -16.67 -2.78
222 St. Andrews 912.88 883.83 -29.04 -3.18
14 Balzan 793.90 709.05 -84.85 -10.69
maisonette - Top 10 locations - Largest median price percentage change
Location Median Q4 2016 Median Q1 2017 Absolute Change (Euro / month) Percentage Change
8 Bahar Ic-Caghaq 650.00 1,100.00 450.00 69.23
30 Birzebbugia 450.00 675.00 225.00 50.00
176 Qawra 500.00 700.00 200.00 40.00
188 Safi 450.00 600.00 150.00 33.33
112 Marsascala 500.00 650.00 150.00 30.00
maisonette - Bottom 10 locations - Smallest median price percentage change
Location Median Q4 2016 Median Q1 2017 Absolute Change (Euro / month) Percentage Change
181 Qrendi 550.00 550.00 0.00 0.00
191 Salina 525.00 525.00 0.00 0.00
222 St. Andrews 900.00 875.00 -25.00 -2.78
138 Mriehel 650.00 600.00 -50.00 -7.69
14 Balzan 700.00 600.00 -100.00 -14.29

Town House Price Changes per Town - Q1 2017 vs Q4 2016

View charts

In [44]:
plot_mmppc_by_ptat('town house', display_charts=False, display_tables=True)
town house - Top 10 locations - Largest mean price percentage change
Location Mean Q4 2016 Mean Q1 2017 Absolute Change (Euro / month) Percentage Change
40 Cospicua 855.00 1,500.00 645.00 75.44
185 Rabat 1,107.14 1,391.67 284.52 25.70
270 Xaghra 476.67 582.50 105.83 22.20
160 Paola 471.67 556.00 84.33 17.88
155 Naxxar 733.33 850.00 116.67 15.91
town house - Bottom 10 locations - Smallest mean price percentage change
Location Mean Q4 2016 Mean Q1 2017 Absolute Change (Euro / month) Percentage Change
262 Victoria 514.00 486.67 -27.33 -5.32
228 St. Julians 1,956.25 1,850.00 -106.25 -5.43
52 Ghajnsielem 525.00 476.00 -49.00 -9.33
68 Gwardamangia 798.75 711.25 -87.50 -10.95
257 Valletta 5,566.64 4,320.00 -1,246.64 -22.39
town house - Top 10 locations - Largest median price percentage change
Location Median Q4 2016 Median Q1 2017 Absolute Change (Euro / month) Percentage Change
40 Cospicua 500.00 1,500.00 1,000.00 200.00
185 Rabat 800.00 1,225.00 425.00 53.12
57 Gharb 525.00 625.00 100.00 19.05
155 Naxxar 750.00 875.00 125.00 16.67
220 Sliema 1,500.00 1,700.00 200.00 13.33
town house - Bottom 10 locations - Smallest median price percentage change
Location Median Q4 2016 Median Q1 2017 Absolute Change (Euro / month) Percentage Change
17 Balzan 900.00 900.00 0.00 0.00
134 Mosta 1,275.00 1,250.00 -25.00 -1.96
52 Ghajnsielem 550.00 500.00 -50.00 -9.09
257 Valletta 2,000.00 1,800.00 -200.00 -10.00
262 Victoria 550.00 485.00 -65.00 -11.82

Terraced House Price Changes per Town - Q1 2017 vs Q4 2016

View charts

In [45]:
plot_mmppc_by_ptat('terraced house', display_charts=False, display_tables=True)
terraced house - Top 10 locations - Largest mean price percentage change
Location Mean Q4 2016 Mean Q1 2017 Absolute Change (Euro / month) Percentage Change
219 Sliema 825.00 950.00 125.00 15.15
154 Naxxar 802.86 908.75 105.89 13.19
242 Swieqi 1,712.65 1,835.71 123.07 7.19
56 Gharb 443.75 475.00 31.25 7.04
163 Pembroke 1,557.14 1,635.71 78.57 5.05
terraced house - Bottom 10 locations - Smallest mean price percentage change
Location Mean Q4 2016 Mean Q1 2017 Absolute Change (Euro / month) Percentage Change
277 Xewkija 626.88 617.86 -9.02 -1.44
51 Ghajnsielem 783.33 750.00 -33.33 -4.26
4 Attard 1,203.46 1,141.92 -61.54 -5.11
184 Rabat 1,040.00 930.00 -110.00 -10.58
203 San Pawl Tat-Targa 1,400.00 1,233.33 -166.67 -11.90
terraced house - Top 10 locations - Largest median price percentage change
Location Median Q4 2016 Median Q1 2017 Absolute Change (Euro / month) Percentage Change
154 Naxxar 650.00 850.00 200.00 30.77
56 Gharb 425.00 500.00 75.00 17.65
16 Balzan 1,000.00 1,100.00 100.00 10.00
26 Birkirkara 875.00 950.00 75.00 8.57
219 Sliema 775.00 825.00 50.00 6.45
terraced house - Bottom 10 locations - Smallest median price percentage change
Location Median Q4 2016 Median Q1 2017 Absolute Change (Euro / month) Percentage Change
20 Bidnija 1,100.00 1,100.00 0.00 0.00
303 Zurrieq 1,000.00 1,000.00 0.00 0.00
287 Zabbar 800.00 750.00 -50.00 -6.25
277 Xewkija 600.00 550.00 -50.00 -8.33
184 Rabat 1,000.00 900.00 -100.00 -10.00

Villa Price Changes per Town - Q1 2017 vs Q4 2016

View charts

In [46]:
plot_mmppc_by_ptat('villa', display_charts=False, display_tables=True)
villa - Top 10 locations - Largest mean price percentage change
Location Mean Q4 2016 Mean Q1 2017 Absolute Change (Euro / month) Percentage Change
5 Attard 2,318.18 2,850.00 531.82 22.94
243 Swieqi 2,255.00 2,696.15 441.15 19.56
196 San Gwann 2,063.92 2,248.54 184.62 8.94
164 Pembroke 1,920.00 2,075.00 155.00 8.07
229 St. Julians 2,188.46 2,265.38 76.92 3.51
villa - Bottom 10 locations - Smallest mean price percentage change
Location Mean Q4 2016 Mean Q1 2017 Absolute Change (Euro / month) Percentage Change
22 Birguma 4,166.67 4,166.67 0.00 0.00
235 St. Paul'S Bay 3,718.00 3,718.00 0.00 0.00
247 Ta' Xbiex 4,595.33 4,595.33 0.00 0.00
18 Balzan 2,100.00 2,066.67 -33.33 -1.59
135 Mosta 2,483.33 2,100.00 -383.33 -15.44
villa - Top 10 locations - Largest median price percentage change
Location Median Q4 2016 Median Q1 2017 Absolute Change (Euro / month) Percentage Change
204 San Pawl Tat-Targa 2,850.00 3,400.00 550.00 19.30
229 St. Julians 1,800.00 2,000.00 200.00 11.11
89 Kappara 2,100.00 2,250.00 150.00 7.14
5 Attard 2,500.00 2,650.00 150.00 6.00
243 Swieqi 2,000.00 2,100.00 100.00 5.00
villa - Bottom 10 locations - Smallest median price percentage change
Location Median Q4 2016 Median Q1 2017 Absolute Change (Euro / month) Percentage Change
235 St. Paul'S Bay 2,200.00 2,200.00 0.00 0.00
18 Balzan 1,700.00 1,700.00 0.00 0.00
247 Ta' Xbiex 4,658.00 4,658.00 0.00 0.00
135 Mosta 2,200.00 2,150.00 -50.00 -2.27
178 Qawra 932.50 815.00 -117.50 -12.60

Bungalow Price Changes per Town - Q1 2017 vs Q4 2016

View charts

In [47]:
plot_mmppc_by_ptat('bungalow', display_charts=False, display_tables=True)
bungalow - Top 10 locations - Largest mean price percentage change
Location Mean Q4 2016 Mean Q1 2017 Absolute Change (Euro / month) Percentage Change
19 Bidnija 2,575.00 2,700.00 125.00 4.85
1 Attard 1,798.67 1,798.67 0.00 0.00
7 Bahar Ic-Caghaq 4,866.67 4,866.67 0.00 0.00
86 Kappara 2,183.33 2,183.33 0.00 0.00
101 Madliena 5,000.00 5,000.00 0.00 0.00
bungalow - Bottom 10 locations - Smallest mean price percentage change
Location Mean Q4 2016 Mean Q1 2017 Absolute Change (Euro / month) Percentage Change
200 San Pawl Tat-Targa 4,300.00 4,300.00 0.00 0.00
275 Xewkija 830.00 830.00 0.00 0.00
121 Mellieha 3,658.42 3,602.86 -55.56 -1.52
80 Iklin 3,350.00 3,250.00 -100.00 -2.99
29 Birzebbugia 2,366.67 2,025.00 -341.67 -14.44
bungalow - Top 10 locations - Largest median price percentage change
Location Median Q4 2016 Median Q1 2017 Absolute Change (Euro / month) Percentage Change
19 Bidnija 2,550.00 2,600.00 50.00 1.96
1 Attard 2,000.00 2,000.00 0.00 0.00
7 Bahar Ic-Caghaq 5,000.00 5,000.00 0.00 0.00
86 Kappara 2,500.00 2,500.00 0.00 0.00
101 Madliena 5,500.00 5,500.00 0.00 0.00
bungalow - Bottom 10 locations - Smallest median price percentage change
Location Median Q4 2016 Median Q1 2017 Absolute Change (Euro / month) Percentage Change
200 San Pawl Tat-Targa 4,500.00 4,500.00 0.00 0.00
275 Xewkija 815.00 815.00 0.00 0.00
121 Mellieha 2,647.50 2,500.00 -147.50 -5.57
80 Iklin 2,950.00 2,750.00 -200.00 -6.78
29 Birzebbugia 1,800.00 1,450.00 -350.00 -19.44