Classification of Nepal’s 2015 Earthquake Damage Levels with Gradient Boosting and SMOTE
By: Atheer Malki
Tags: Featureselection, Gradient Boosting, Smote

In 2015, Nepal faced a series of unfortunate disasters in the form of earthquake Gorkha and the avalanches it triggered. Many homes, families, and lives were damaged and lost.

In this post, the grade of damage inflicted upon 347,469 buildings in Nepal is analyzed. A Gradient Boosting Classifier is used for prediction. Feature Selection and SMOTE are then performed to improve results.

DATA

The data used consists of 347,469 rows, 38 features and 1 target. The features provide various descriptions of each building, such as its geographical position, construction materials, height, usages, and more.

The target damage_grade is of type integer and it represents the grade of damage a building has endured. The three classes of damage are:

1, indicating low levels of damage

2, indicating medium levels of damage

3, indicating high levels of damage or destruction.

VISUALIZATION

In this part, we will use Seaborn to visualize the data and understand the relations between the features. Seaborn is a visualization library used in python, it operates as a wrapper for matplotlib.

import seaborn as sns

First off, let’s plot a heatmap to view the correlation between features.

corr = df.drop('building_id', axis=1).corr()

fig, ax = plt.subplots(figsize=(10,8))

ax.set_title("Feature Correlation Heatmap")

sns.heatmap(corr, ax=ax)

In the code above, we simply use .corr() to get all correlations between the features then we plot them. This heatmap represents correlation as a number from 1 to -0.4 with 1 indicating greater correlation.

A heatmap depicting the correlation between features

Observations:

There seems to be a correlation between the features geo_level_1_id and foundation_type, also between area_percentage and ground_floor_type, interesting.

Another nice revelation is seen in the correlations of the feature has_secondary_use, as it seems that the most common secondary uses in order are: agriculture, hotel, rental, and institution.

Some obvious correlations can also be seen such as the one between the building’s height and the count of floors.

Next, plot the relationship between a building's area and height with the distinction of the damage grade

sns.relplot(

data=train_values,

x="area_percentage", y="height_percentage",

hue="damage_grade", aspect=11.7/9

).fig.suptitle("Height vs. Area", y=1)

The parameters specified for this relation plot are as follows; data to, well, specify the data we’ll plot from. x and y to specify our axes. hue to control the colour of the plotted points, this adds new information to the plot. lastly, aspect to choose the size of the plot.

A relation plot between a building’s area and height

Observations:

There are many purple points! Could this mean that there are more buildings classified with damage_grade 2 compared to 1 and 3 in the dataset? If so, this might indicate the presence of class imbalance.

Looking at the spread of points, the majority of data points are on the bottom-left side of the plot, yet some values seem to be distant. This might indicate the presence of outliers.

Other than this, we can see in the top-left of the plot there are several buildings with a high height_percentage and relatively smaller area_percentage that appear to be classified with damage_grade 3. It seems logical to assume the higher the building and the smaller its area is, the more damage it will face.

Next, is a barplot showing how many instances of each class there are.

sns.catplot(x='damage_grade', data=df, kind='count', palette=sns.color_palette(['green','purple', 'blue'])).fig.suptitle("Count of Class Instances", y=1.01)

The code above creates a catplot of the kind ‘count’. This provides a visualization of the number of instances there are for each value in the ‘damage_grade’ class.

Plot visualizing the number of instances of each class

According to this plot, there is a multiclass imbalance. This means that in this dataset there are more instances where the damage_grade is 2, but fewer ones where the damage_grade is 1 or 3.

This is a problem because when we use such data to train a machine learning model, the model will face difficulty predicting the minority class later on since the data was not sufficient. One way to fix this is by using SMOTE.

With SMOTE (Synthetic Minority Over-sampling Technique), either oversampling or undersampling can be used on certain classes.

PREPROCESSING

Post-inspection, the data appears to have no missing values. Great. However, many columns are of a categorical type. We will deal with this using one-hot encoding & ordinal encoding; depending on the number of classes each column has.

One-hot encoding creates a new flag column for each class in the column. Using this on a column with a large number of classes will increase the size of our data and make it difficult to deal with. Ordinal encoding simply assigns a numerical value to each class. This does not affect the size of data yet when training, our model will regard those numbers differently. For instance, 2 will be thought of as less than 3 when in reality those numbers represent categorical values that have no precedence.

# use OrdinalEncoder to deal with columns of 4 or more classes

four_more = ['foundation_type', 'ground_floor_type', 'plan_configuration','other_floor_type',

'position', 'legal_ownership_status'] enc = OrdinalEncoder() df[four_more] = enc.fit_transform(df[four_more]) # use one-hot encoding to deal with columns of 3 or less classes

three_less = ['land_surface_condition', 'roof_type']

df = pd.get_dummies(df, columns=three_less)

Now all data is in numerical form.

Moving on to outlier detection and removal. The anomaly detection algorithm IsolationForest is used.

# detect outliers using isloation forset

iso = IsolationForest(contamination=0.1)

yhat = iso.fit_predict(X_train)



# select only rows that are not outliers

mask = yhat != -1

X_train, y_train = X_train.loc[mask, :], y_train.loc[mask] # output

before outlier removal: (182420, 43) (182420,)

after outlier removal: (164178, 43) (164178,)

This reveals that 18242 rows containing outlier values were detected and removed from the data frame.

Prediction

Gradient Boosting Classifier is an iterative functional gradient algorithm, it is a type of machine learning boosting.

GBI = GradientBoostingClassifier(

learning_rate=0.01,

max_depth=4,

n_estimators = 500,

max_features= "sqrt",

verbose = 1

)

Here is what each parameter means:

learning_rate: shrinks the contribution of each tree by learning_rate. There is a trade-off between learning_rate and n_estimators.

max_depth: limits the number of nodes in the tree. Tune this parameter for best performance; the best value depends on the interaction of the input variables.

n_estimators: The number of boosting stages to perform. Gradient boosting is fairly robust to over-fitting so a large number usually results in better performance.

min_samples_split: The minimum number of samples required to split an internal node.

max_features: The number of features to consider when looking for the best split

verbose: If 1 then it prints progress and performance once in a while (the more trees the lower the frequency).

Our data is split into training and testing sets that are then fit to the model. After that, a prediction is made and tested.

This process was repeated 3 times. Firstly using GBI only, secondly using GBI with feature selection, thirdly using GBI with feature selection and SMOTE.

Plotting feature importances

The plot above demonstrates just how relevant each feature was when the model making a decision.