Skip to content

Practical Exam: House sales

RealAgents is a real estate company that focuses on selling houses.

RealAgents sells a variety of types of house in one metropolitan area.

Some houses sell slowly and sometimes require lowering the price in order to find a buyer.

In order to stay competitive, RealAgents would like to optimize the listing prices of the houses it is trying to sell.

They want to do this by predicting the sale price of a house given its characteristics.

If they can predict the sale price in advance, they can decrease the time to sale.

Data

The dataset contains records of previous houses sold in the area.

Column NameCriteria
house_idNominal.
Unique identifier for houses.
Missing values not possible.
cityNominal.
The city in which the house is located. One of 'Silvertown', 'Riverford', 'Teasdale' and 'Poppleton'.
Replace missing values with "Unknown".
sale_priceDiscrete.
The sale price of the house in whole dollars. Values can be any positive number greater than or equal to zero.
Remove missing entries.
sale_dateDiscrete.
The date of the last sale of the house.
Replace missing values with 2023-01-01.
months_listedContinuous.
The number of months the house was listed on the market prior to its last sale, rounded to one decimal place.
Replace missing values with mean number of months listed, to one decimal place.
bedroomsDiscrete.
The number of bedrooms in the house. Any positive values greater than or equal to zero.
Replace missing values with the mean number of bedrooms, rounded to the nearest integer.
house_typeOrdinal.
One of "Terraced" (two shared walls), "Semi-detached" (one shared wall), or "Detached" (no shared walls).
Replace missing values with the most common house type.
areaContinuous.
The area of the house in square meters, rounded to one decimal place.
Replace missing values with the mean, to one decimal place.

Task 1

The team at RealAgents knows that the city that a property is located in makes a difference to the sale price.

Unfortuntately they believe that this isn't always recorded in the data.

Calculate the number of missing values of the city.

  • You should use the data in the file "house_sales.csv".

  • Your output should be an object missing_city, that contains the number of missing values in this column.

import pandas as pd
import numpy as np

# Load the dataset, setting 'house_id' as the index and parsing 'sale_date' as datetime
houses = pd.read_csv('house_sales.csv', index_col='house_id', parse_dates=['sale_date'])

# Replace placeholder '--' with NaN to mark missing values in the 'city' column
houses.city = houses.city.replace('--', np.nan)

# Calculate the number of missing values in the 'city' column
missing_city = houses.city.isna().sum()

# Optionally fill missing 'city' values with 'Unknown'
houses.city = houses.city.fillna('Unknown')

Task 2

Before you fit any models, you will need to make sure the data is clean.

The table below shows what the data should look like.

Create a cleaned version of the dataframe.

  • You should start with the data in the file "house_sales.csv".

  • Your output should be a dataframe named clean_data.

  • All column names and values should match the table below.

Column NameCriteria
house_idNominal.
Unique identifier for houses.
Missing values not possible.
cityNominal.
The city in which the house is located. One of 'Silvertown', 'Riverford', 'Teasdale' and 'Poppleton'
Replace missing values with "Unknown".
sale_priceDiscrete.
The sale price of the house in whole dollars. Values can be any positive number greater than or equal to zero.
Remove missing entries.
sale_dateDiscrete.
The date of the last sale of the house.
Replace missing values with 2023-01-01.
months_listedContinuous.
The number of months the house was listed on the market prior to its last sale, rounded to one decimal place.
Replace missing values with mean number of months listed, to one decimal place.
bedroomsDiscrete.
The number of bedrooms in the house. Any positive values greater than or equal to zero.
Replace missing values with the mean number of bedrooms, rounded to the nearest integer.
house_typeOrdinal.
One of "Terraced", "Semi-detached", or "Detached".
Replace missing values with the most common house type.
areaContinuous.
The area of the house in square meters, rounded to one decimal place.
Replace missing values with the mean, to one decimal place.
import pandas as pd
import numpy as np

# Read the CSV file into a DataFrame, parsing 'sale_date' as datetime
houses = pd.read_csv('house_sales.csv', parse_dates=['sale_date'])

# Create a copy of the DataFrame to clean the data
clean_data = houses.copy()

# Replace '--' in the 'city' column with 'Unknown'
clean_data.city = clean_data.city.replace('--', 'Unknown')

# Fill missing values in 'months_listed' with the mean of the column, rounded to 1 decimal place
clean_data.months_listed = clean_data.months_listed.fillna(clean_data.months_listed.mean().round(1))

# Replace abbreviations in 'house_type' with full names
clean_data.house_type = clean_data.house_type.replace({
    'Det.': 'Detached',
    'Semi': 'Semi-detached',
    'Terr.': 'Terraced'
})

# Remove ' sq.m.' from 'area' column and convert it to float
clean_data.area = clean_data.area.str.strip(' sq.m.').astype('float')

Task 3

The team at RealAgents have told you that they have always believed that the number of bedrooms is the biggest driver of house price.

Producing a table showing the difference in the average sale price by number of bedrooms along with the variance to investigate this question for the team.

  • You should start with the data in the file 'house_sales.csv'.

  • Your output should be a data frame named price_by_rooms.

  • It should include the three columns bedrooms, avg_price, var_price.

  • Your answers should be rounded to 1 decimal place.

import pandas as pd
import numpy as np

# Read the CSV file into a DataFrame, parsing 'sale_date' as datetime
houses = pd.read_csv('house_sales.csv', parse_dates=['sale_date'])

# Group by 'bedrooms' and calculate the mean and variance of 'sale_price'
price_by_rooms = houses.groupby('bedrooms', as_index=False).agg(
    avg_price=('sale_price', 'mean'),
    var_price=('sale_price', 'var')
).round(1)

Task 4

Fit a baseline model to predict the sale price of a house.

  1. Fit your model using the data contained in “train.csv”

  2. Use “validation.csv” to predict new values based on your model. You must return a dataframe named base_result, that includes house_id and price. The price column must be your predicted values.

import pandas as pd
import numpy as np

from sklearn.model_selection import KFold, cross_val_score
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.metrics import mean_squared_error
from sklearn.preprocessing import OneHotEncoder, MinMaxScaler

from sklearn.linear_model import LinearRegression

# Load training and validation datasets
train = pd.read_csv('train.csv', index_col='house_id', parse_dates=['sale_date'])
test = pd.read_csv('validation.csv', index_col='house_id', parse_dates=['sale_date'])

# Extract year, month, and day from 'sale_date' for both datasets
for df in [train, test]:
    df['year'] = df.sale_date.dt.year - 2019
    df['month'] = df.sale_date.dt.month
    df['day'] = df.sale_date.dt.day

# Define categorical and numerical features
category_features = ['city', 'house_type']
numeric_features = ['months_listed', 'bedrooms', 'area', 'year', 'month', 'day']

# Separate features and target variable from training data
X = train.drop(columns=['sale_price', 'sale_date'])
y = train['sale_price']
X_test = test.drop(columns=['sale_date'])

# Define preprocessing steps for numerical and categorical features
preprocessing = ColumnTransformer([
    ('num', MinMaxScaler(), numeric_features),
    ('cat', OneHotEncoder(handle_unknown='ignore'), category_features)
])

# Initialize linear regression model
linreg = LinearRegression()

# Create a pipeline with preprocessing and model
pipeline = Pipeline([
    ('preprocessing', preprocessing),
    ('linreg', linreg)
])

# Perform cross-validation to evaluate the model
kf = KFold(n_splits=6, shuffle=True, random_state=301)
cv_scores = cross_val_score(pipeline, X, y, cv=kf, n_jobs=-1, scoring='neg_root_mean_squared_error')

# Fit the model on the entire training data
pipeline.fit(X, y)

# Calculate RMSE on the training data
train_rmse = mean_squared_error(y, pipeline.predict(X), squared=False)

# Predict house prices on the validation data
prediction = np.maximum(0, pipeline.predict(X_test))

# Create a DataFrame with house_id and predicted prices
base_result = pd.DataFrame({
    "house_id": X_test.index,
    "price": np.round(prediction, 2)
})

# Print the first few rows of the result and RMSE scores
print(base_result.head())
print(f'\nCV RMSE: {abs(cv_scores.mean()):.4f}')
print(f'Train RMSE: {train_rmse:.4f}')

Task 5

Fit a comparison model to predict the sale price of a house.

  1. Fit your model using the data contained in “train.csv”

  2. Use “validation.csv” to predict new values based on your model. You must return a dataframe named compare_result, that includes house_id and price. The price column must be your predicted values.

import pandas as pd
import numpy as np

from sklearn.model_selection import KFold, cross_val_score
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.metrics import mean_squared_error
from sklearn.preprocessing import OneHotEncoder, MinMaxScaler

from sklearn.ensemble import RandomForestRegressor

# Load training and validation datasets
train = pd.read_csv('train.csv', index_col='house_id', parse_dates=['sale_date'])
test = pd.read_csv('validation.csv', index_col='house_id', parse_dates=['sale_date'])

# Extract year, month, and day from sale_date
for df in [train, test]:
    df['year'] = df.sale_date.dt.year - 2019
    df['month'] = df.sale_date.dt.month
    df['day'] = df.sale_date.dt.day

# Define categorical and numerical features
category_features = ['city', 'house_type']
numeric_features = ['months_listed', 'bedrooms', 'area', 'year', 'month', 'day']

# Separate features and target variable from training data
X = train.drop(columns=['sale_price', 'sale_date'])
y = train['sale_price']
X_test = test.drop(columns=['sale_date'])

# Define preprocessing steps for numerical and categorical features
preprocessing = ColumnTransformer([
    ('num', MinMaxScaler(), numeric_features),
    ('cat', OneHotEncoder(handle_unknown='ignore'), category_features)
])

# Initialize the RandomForestRegressor model
rf = RandomForestRegressor(random_state=301)

# Create a pipeline that first preprocesses the data and then fits the model
pipeline = Pipeline([
    ('preprocessing', preprocessing),
    ('rf', rf)
])

# Perform cross-validation to evaluate the model
kf = KFold(n_splits=6, shuffle=True, random_state=301)
cv_scores = cross_val_score(pipeline, X, y, cv=kf, n_jobs=-1, scoring='neg_root_mean_squared_error')

# Fit the pipeline on the entire training data
pipeline.fit(X, y)

# Calculate RMSE on the training data
train_rmse = mean_squared_error(y, pipeline.predict(X), squared=False)

# Predict house prices on the validation data
prediction = np.maximum(0, pipeline.predict(X_test))

# Create a DataFrame with house_id and predicted prices
compare_result = pd.DataFrame({
    "house_id": X_test.index,
    "price": np.round(prediction, 2)
})

# Display the first few rows of the result and RMSE scores
print(compare_result.head())
print(f'\nCV RMSE: {abs(cv_scores.mean()):.4f}')
print(f'Train RMSE: {train_rmse:.4f}')

It is enough score to pass the project.
For further improvement of the RF model, we need to deal with overfitting with fine-tuning its parameters and/or feature engineering.