Analysing Singapore's Gross Monthly Income Changes From 2010 to 2020

Introduction

Singapore became the second most expensive place to live in the world, according to the Economic Intelligence Unit (EIU)'s Worldwide Cost of Living 2021 report released on Dec. 1, 2021.
This triggered a lot of online chatter from many Singaporeans. Much of the sentiment is that Singaporeans are always in "survival mode", because price has risen but income has not.
Personally, I do also feel and worry about the financial burden of living in Singapore. This motivated me to do an analysis of the income earned by Singaporeans.

Hunting down the data

First, we need to find the relevant datasets to work with. Usually that means searching on the amazing data.gov.sg. However, that not yield any datasets related to what we are looking for.
Well, the next best alternative is to Google, and I was not disappointed. It led me to MOM's site, specifically here.
There are a lot of amazing data sets here! It would be worth revisiting some of the data sets here in the future for analysis.

Cleaning the data set

It's time to use a bit of Python magic to clean up the data.
Firstly, the data set is available in excel. Since the data set is small, let's convert it to a csv file by hand for analysis using Python.
We will read in the data set using the pandas library in Python.
1
import pandas as pd
2
df = pd.read_csv('gross monthly income.csv', index_col=0)
3
df

Range of Gross Monthly Income By Year
(Excluding Employer CPF) 2010 to 2020 (June)

20102011201220132014201520162017201820192020
Under $50056.35151.344.346.74743.84341.239.547.5
$500 - $999191.4185.3186.7162.8133.3125.9113.7110.9102.2101.9105.8
$1,000 - $1,499238.1227.7216.6209.8235.8234.5225.9218.7210.1198.6202.1
$1,500 - $1,999236233.2223.9210.9211.8203.9203.8189.6186.1172172
$2,000 - $2,499215.9211.7207.3203.6201.1205.8204.7203.1200191.8187.4
$2,500 - $2,999161.1159.1152.2156.8161.7164.3166.1151.7163169.8157
$3,000 - $3,999252.2259.6269.3277.6286.1294.4299.3306.1309.3317.1313.4
$4,000 - $4,999153.1161.6176.6188.4185.7194.7208.3211.9223231.6226.1
$5,000 - $5,999114.6122.5136.3144.8147.1157.2160.6166.6167.1175.7179.4
$6,000 - $6,99962.971.876.985.685.394.896.7103106.4108.9109.6
$7,000 - $7,99948.352.958.460.367.972.279.780.884.192.592.3
$8,000 - $8,99938.342.546.552.354.359.165.565.467.271.276.9
$9,000 - $9,99921.928.3293338.14040.743.647.153.551.1
$10,000 - $10,99927.331.933.440.643.240.743.850.254.359.357.5
$11,000 - $11,99913.71517.719.621.623.724.827.529.629.532
$12,000 - $12,99912.213.514.615.418.32120.422.126.326.224.1
$13,000 - $13,9997.78.81011.914.114.314.116.815.918.219.1
$14,000 - $14,9996.37.3799.81310.911.814.41414.5
$15,000 - $19,99923.224.829.434.337.639.740.546.249.350.350.7
$20,000 & Over31.337.745.143.852.251.549.756.558.162.462
Total1911.61946.519882004.62051.82097.52112.92125.42154.92184.22180.4
Value in Thousands
(Exclude Full-Time National Servicemen) Aged fifteen years and over

Limitations

While we have found a good data set to work with, there are a few limitations that we need to consider in our analysis.
  1. The data is presented in ranges/buckets. This means that the true value might be obfuscated within the ranges provided. i.e. $3,001 and $3,998 are all within the income group of $3,000 to $3,999, even if they are quite different.
  2. This data set excludes CPF contributions.
  3. CPI is not factored into our analysis (until later).
Now that we have the raw data and understood our limitations, let's do a few data visualisation to look for patterns and trends.

Analysing Gross Monthly Income

Firstly, let's take a look at the breakdown of the raw number of population per monthly income group in Singapore.
We will look at the comparison of all the values using a stacked bar chart. It should give us an understanding of the breakdown of each income group by year, and at the same time allow us to compare the differences per year.

Gross Monthly Income Bar Chart

Using the bar chart is still a little messy given that there are many gross monthly income ranges in the dataset. We will improve on the charts and visualisation later.
Next, we will also try and take a look at the breakdown by year for each income range. We will use a sunburst chart to group up the data for visualisation, broken down to a pie chart for per income range breakdown.

Gross Median Income Sunburst Chart in 2020

Selected Year: 2020
2020

Visualising Change By Year

We will inspect the changes of each income group by year using a line chart for each income group.

Monthly Median Income Line Chart

Measuring Rate of Change

A better approach would be to express each cell as a percentage of change based on the preceding time period. This will give us a sensing of the rate of change in each income group by year.
1
# 1. get rid of 'total' row
2
# 2. convert cells to percentage change from previous row using pct_change()
3
change_df_by_year = df.drop('Total').pct_change(axis='columns')
4
change_df_by_year.multiply(100) # express as percentage out of 100

Percentage Change of Gross Monthly Income By Time Period

20102011201220132014201520162017201820192020
Under $500--9.41%0.59%-13.65%5.42%0.64%-6.81%-1.83%-4.19%-4.13%20.25%
$500 - $999--3.19%0.76%-12.8%-18.12%-5.55%-9.69%-2.46%-7.84%-0.29%3.83%
$1,000 - $1,499--4.37%-4.87%-3.14%12.39%-0.55%-3.67%-3.19%-3.93%-5.47%1.76%
$1,500 - $1,999--1.19%-3.99%-5.81%0.43%-3.73%-0.05%-6.97%-1.85%-7.58%0%
$2,000 - $2,499--1.95%-2.08%-1.78%-1.23%2.34%-0.53%-0.78%-1.53%-4.1%-2.29%
$2,500 - $2,999--1.24%-4.34%3.02%3.12%1.61%1.1%-8.67%7.45%4.17%-7.54%
$3,000 - $3,999-2.93%3.74%3.08%3.06%2.9%1.66%2.27%1.05%2.52%-1.17%
$4,000 - $4,999-5.55%9.28%6.68%-1.43%4.85%6.99%1.73%5.24%3.86%-2.37%
$5,000 - $5,999-6.89%11.27%6.24%1.59%6.87%2.16%3.74%0.3%5.15%2.11%
$6,000 - $6,999-14.15%7.1%11.31%-0.35%11.14%2%6.51%3.3%2.35%0.64%
$7,000 - $7,999-9.52%10.4%3.25%12.6%6.33%10.39%1.38%4.08%9.99%-0.22%
$8,000 - $8,999-10.97%9.41%12.47%3.82%8.84%10.83%-0.15%2.75%5.95%8.01%
$9,000 - $9,999-29.22%2.47%13.79%15.45%4.99%1.75%7.13%8.03%13.59%-4.49%
$10,000 - $10,999-16.85%4.7%21.56%6.4%-5.79%7.62%14.61%8.17%9.21%-3.04%
$11,000 - $11,999-9.49%18%10.73%10.2%9.72%4.64%10.89%7.64%-0.34%8.47%
$12,000 - $12,999-10.66%8.15%5.48%18.83%14.75%-2.86%8.33%19%-0.38%-8.02%
$13,000 - $13,999-14.29%13.64%19%18.49%1.42%-1.4%19.15%-5.36%14.47%4.95%
$14,000 - $14,999-15.87%-4.11%28.57%8.89%32.65%-16.15%8.26%22.03%-2.78%3.57%
$15,000 - $19,999-6.9%18.55%16.67%9.62%5.59%2.02%14.07%6.71%2.03%0.8%
$20,000 & Over-20.45%19.63%-2.88%19.18%-1.34%-3.5%13.68%2.83%7.4%-0.64%
Visually, it will be easier on the eye if we present the data in the form of a heatmap.
1
import seaborn as sns
2
sns.set_theme()
3
import matplotlib.pyplot as plt
4
5
grid_kws = {"height_ratios": (.9, .05), "hspace": .1}
6
f, (ax, cbar_ax) = plt.subplots(2, gridspec_kw=grid_kws, figsize=(25,10))
7
8
ax = sns.heatmap(change_df, ax=ax, annot=True, fmt=".2f", linewidths=.5, cbar_ax=cbar_ax,
9
cbar_kws={"orientation": "horizontal"}, center=0)
10
11
ax.xaxis.set_ticks_position("top")

Heat Map of Gross Monthly Income Percentage Change By Time Period

By visualising the heat map data set in a yearly period, we could see that most of the reds (decrease) are located in the lower income ranges, while most of the greens (increase) are located in the higher income groups.
That is a good sign! It means we have fewer people in the lower income groups each year (pre 2020), and more workers in Singapore entering the higher income groups. Particularly in the period from 2016 to 2019, the data shows sustained percentage decrease across each of the income ranges below $2,500.
However, 2020 has reversed the trend (definitely due to COVID-19). We could see percentage increases for all income groups below $2,000. In particular, there is a worrying 20.25% increase in the under $500 earners from year 2019 to 2020. On a side note, I'm now interested to analyse the impact of COVID 19 on Singaporean's income in a separate article.
If we take a look at the heatmap in a 10 year period (from 2010 to 2020), the data set is definitely healthy. We could see a decrease in the lower income groups that are under $3,000, and an increase in growth from $3,000 and above. This means that there are more Singaporeans are earning $3,000 and above.
But, it does seem like the percentage growth increase in the higher income groups is disproportionate to the percentage decrease in the lower income brackets. This could be a sign of income inequality, which we could try to explore later.

Gross Monthly Income Group of Singaporeans by Percentile

Great, we should also analyse the income buckets by percentile. This should give us a sense of income changes in each percentage group of Singaporeans.
1
year_range = range(2010, 2021)
2
3
def get_percentile_value(series, p):
4
total = series.sum()
5
return total/100*p
6
7
def find_bucket(series, value):
8
sum = 0
9
idx = 0
10
for val in series:
11
sum += val
12
if sum >= value:
13
return idx
14
idx += 1
15
16
def bucket_for_year(df, year, p):
17
series = df[year]
18
return df.index[find_bucket(series, get_percentile_value(series, p))]
19
20
def format_percentile(p):
21
if p > 50:
22
return "Top {}%".format(100 - p)
23
elif p < 50:
24
return "Bottom {}%".format(p)
25
else:
26
return "Median (50%)"
27
28
def construct_median_pds(df, percentiles):
29
values = {}
30
for p in percentiles:
31
data = []
32
for y in year_range:
33
bucket = bucket_for_year(df, str(y), p)
34
data.append(bucket)
35
values[format_percentile(p)] = data
36
return pd.DataFrame(data=values, index=year_range)
37
38
percentile_df = construct_median_pds(data.drop('Total'), [5, 10, 25, 50, 75, 90, 95])
39
percentile_df

Gross Monthly Income Range by Percentage of Singapore Population

Bottom 5%Bottom 10%Bottom 25%Median (50%)Top 25%Top 10%Top 5%
2010$500 - $999$500 - $999$1,000 - $1,499$2,500 - $2,999$4,000 - $4,999$7,000 - $7,999$10,000 - $10,999
2011$500 - $999$500 - $999$1,500 - $1,999$2,500 - $2,999$4,000 - $4,999$8,000 - $8,999$11,000 - $11,999
2012$500 - $999$500 - $999$1,500 - $1,999$2,500 - $2,999$5,000 - $5,999$8,000 - $8,999$12,000 - $12,999
2013$500 - $999$500 - $999$1,500 - $1,999$3,000 - $3,999$5,000 - $5,999$9,000 - $9,999$12,000 - $12,999
2014$500 - $999$1,000 - $1,499$1,500 - $1,999$3,000 - $3,999$5,000 - $5,999$9,000 - $9,999$13,000 - $13,999
2015$500 - $999$1,000 - $1,499$1,500 - $1,999$3,000 - $3,999$5,000 - $5,999$9,000 - $9,999$13,000 - $13,999
2016$500 - $999$1,000 - $1,499$1,500 - $1,999$3,000 - $3,999$5,000 - $5,999$9,000 - $9,999$13,000 - $13,999
2017$500 - $999$1,000 - $1,499$1,500 - $1,999$3,000 - $3,999$5,000 - $5,999$10,000 - $10,999$14,000 - $14,999
2018$500 - $999$1,000 - $1,499$1,500 - $1,999$3,000 - $3,999$6,000 - $6,999$10,000 - $10,999$14,000 - $14,999
2019$500 - $999$1,000 - $1,499$2,000 - $2,499$3,000 - $3,999$6,000 - $6,999$10,000 - $10,999$15,000 - $19,999
2020$500 - $999$1,000 - $1,499$2,000 - $2,499$3,000 - $3,999$6,000 - $6,999$10,000 - $10,999$15,000 - $19,999
So far, we have not used CPI (consumer price index) in our analysis of the data. We will take a look at CPI in the next section.

CPI

Consumer Price Index (CPI) is designed to measure the average price changes of a fixed basket of consumption goods and services commonly purchased by the resident households over time. It is widely used as a measure of consumer price inflation.
Quite simply put, it helps us to measure the relative value of money between different years, i.e. what can $500 get you in 2010 vs in 2020.
In our analysis, let's make use of CPI to calculate the income earned by each percentile relative to the value of money in year 2020.

Calculating Income Ranges of Different Percentiles using CPI

We start by getting the CPI values from data.gov.sg. We will convert the indexes to switch the base year from 2019 to 2020.

CPI Value with 2020 as Base Year

20102011201220132014201520162017201820192020
CPI87.56792.16396.3898.65499.66499.14398.61599.18599.619100.182100
To calculate the adjusted wage, we can make use of the following formula:
\(\text{wage}_\text{target_year} = \frac{\text{wage}_\text{year}}{\text{CPI}_\text{year}} * \text{CPI}_\text{target_year}\)
For example, for a wage of $500 in 2010, we can use the formula to find out that its corresponding value is $570.99.
\(\text{wage}_\text{target_year} = \frac{500}{87.567} * 100 \approx $570.99\)
Using this, let's calculate the relative value of each income group using 2020 as the base.
1
import re
2
3
def parse_value(str):
4
str = str.replace(',', '', -1)
5
regex_search = re.findall(r'$(.*) - $(.*)', str)[0]
6
return list(map(lambda x: int(x), regex_search))
7
8
def get_cpi_by_year(year):
9
return round(cpi_df.loc[cpi_df['year'] == year]['cpi'].values[0], 3)
10
11
def convert_value_to_2020_base(value, year):
12
return round(value / get_cpi_by_year(year) * 100, 2)
13
14
def format_currency(value):
15
return "{:,.2f}".format(value)
16
17
def format_values(values):
18
return "${} - ${}".format(format_currency(values[0]), format_currency(values[1]))
19
20
cpi_2020_df = percentile_df.copy()
21
22
for col in percentile_df:
23
for year in percentile_df.index:
24
values = format_values(
25
list(
26
map(lambda x: convert_value_to_2020_base(x, year), parse_value(percentile_df[col][year]))
27
)
28
)
29
30
cpi_2020_df[col][year] = values
31
32
cpi_2020_df

Gross Monthly Income Range by Percentage of Singapore Population using CPI with 2020 as Base Year

Bottom 5%Bottom 10%Bottom 25%Median (50%)Top 25%Top 10%Top 5%
2010$570.99 - $1,140.84$570.99 - $1,140.84$1,141.98 - $1,711.83$2,854.96 - $3,424.81$4,567.93 - $5,708.77$7,993.88 - $9,134.72$11,419.83 - $12,560.67
2011$542.52 - $1,083.95$542.52 - $1,083.95$1,627.55 - $2,168.98$2,712.59 - $3,254.02$4,340.14 - $5,424.09$8,680.27 - $9,764.22$11,935.38 - $13,019.32
2012$518.78 - $1,036.52$518.78 - $1,036.52$1,556.34 - $2,074.08$2,593.90 - $3,111.64$5,187.80 - $6,224.32$8,300.48 - $9,337$12,450.72 - $13,487.24
2013$506.82 - $1,012.63$506.82 - $1,012.63$1,520.47 - $2,026.27$3,040.93 - $4,053.56$5,068.22 - $6,080.85$9,122.79 - $10,135.42$12,163.72 - $13,176.35
2014$501.69 - $1,002.37$1,003.37 - $1,504.05$1,505.06 - $2,005.74$3,010.11 - $4,012.48$5,016.86 - $6,019.22$9,030.34 - $10,032.71$13,043.83 - $14,046.20
2015$504.32 - $1,007.64$1,008.64 - $1,511.96$1,512.97 - $2,016.28$3,025.93 - $4,033.57$5,043.22 - $6,050.86$9,077.80 - $10,085.43$13,112.37 - $14,120.01
2016$507.02 - $1,013.03$1,014.04 - $1,520.05$1,521.07 - $2,027.07$3,042.13 - $4,055.16$5,070.22 - $6,083.25$9,126.40 - $10,139.43$13,182.58 - $14,195.61
2017$504.11 - $1,007.21$1,008.22 - $1,511.32$1,512.33 - $2,015.43$3,024.65 - $4,031.86$5,041.08 - $6,048.29$10,082.17 - $11,089.38$14,115.04 - $15,122.25
2018$501.91 - $1,002.82$1,003.82 - $1,504.73$1,505.74 - $2,006.65$3,011.47 - $4,014.29$6,022.95 - $7,025.77$10,038.25 - $11,041.07$14,053.54 - $15,056.36
2019$499.09 - $997.19$998.18 - $1,496.28$1,996.37 - $2,494.46$2,994.55 - $3,991.74$5,989.10 - $6,986.28$9,981.83 - $10,979.02$14,972.75 - $19,962.67
2020$500 - $999$1,000 - $1,499$2,000 - $2,499$3,000 - $3,999$6,000 - $6,999$10,000 - $10,999$15,000 - $19,999

Median (50th Percentile) Earner

Looking at the median (50th percentile), there is definitely an increase from $2,854.96 - $3,424.81 in 2010 to $3,000 - $3,999 in 2020.
The increase is not as big as I had imagined, but it's a clear sign that Singaporeans are earning more than before.
Another thing to note is that the growth seems to have stagnated from 2013 onwards to 2020. However, it should also be noted that the data is presented in ranges. So there might be growth, for example from $3,001 to $3,998, but is not readily represented by the data set.

Income Inequality

Taking inflation into account, the data doesn't look good for the 5th and 10th percentile in Singapore. The bottom 5% population has in fact regressed from 2010 to 2020.
On the other hand, the 95th (top 5%) percentile has seen a huge increase in the income range from $11,419.83 - $12,560.67 in 2010 to $15,000 - $19,999 in 2020.
This is definitely a clear sign of growing income inequality in Singapore, with the rich getting richer and the poor becoming poorer.

Conclusion

The data looks worrying for the population earning under the median. Income growth has largely stagnated, and you could even argue that it has regressed when you take CPI into account.
On the other hand, everybody above the 50th percentile has seen their income levels soar. You could see sustained income growth for each of the percentile groups (25%, 10%, 5%).

Afterword

Well, that's all for now. This is actually the first article I have written. I've decided to start this blog to express my thoughts on technology, data science and politics (to a lesser degree).
If you are interested in articles like this, I would really recommend you subscribe to my telegram channel. It's completely anonymous, and you will receive instant notifications when I have new articles.
Also, if you are interested to learn coding and data science, I teach at UpCode Academy in Singapore. Courses can be fully covered under Skillsfuture. Check out the website for more information!
Lastly, if you have any leads or interesting things that you think I might be able to analyse and write about, feel free to reach out to me on my social media! Any feedback is also welcomed!
Subscribe to my telegram channel (anonymously) to get updates when I post.