CA2 Assignment - How do polytechnic and university graduates in Singapore compare in terms of starting salaries and employment rates from 2016 to 2023¶
Introduction¶
This report compares polytechnic and university graduates in Singapore from 2016 to 2023, focusing on starting salaries and employment rates. Using data from the Graduate Employment Survey and MOM, it aims to help students make informed decisions about their education and future careers.
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
from matplotlib.lines import Line2D
Data Gathering, Cleaning, and Structuring for Data set 2¶
1. Data Sources Used¶
The dataset Polytechnic_GES_2016_2023_all_clusters.csv was compiled using official government sources that report on the employment outcomes of polytechnic graduates in Singapore. The primary sources include:
- Graduate Employment Survey (GES) media releases (e.g. GES 2023 Media Release – np.edu.sg)
- Ministry of Manpower (MOM): Yearbook of Manpower Statistics, specifically Table H.6 (2016–2020) from stats.mom.gov.sg
- Additional validation through data.gov.sg and SGCharts where relevant
These sources provided:
- Employment rates (% employed within 6 months)
- Median gross monthly starting salaries
Data was collected separately for:
- Fresh Graduates
- Post-National Service (Post-NS) Graduates
And is categorized across the following 8 official course clusters used in GES reporting:
- Engineering
- Business
- Information & Digital Technologies
- Health Sciences
- Humanities & Social Sciences
- Sciences
- Built Environment
- Arts, Design & Media
2. Cleaning and Structuring Process¶
To prepare the dataset for analysis, the following steps were taken:
| Step | Description |
|---|---|
| Extraction | Data from 2016–2020 was extracted from MOM Excel tables (Table H.6); 2021–2023 values were sourced from GES PDF reports. |
| Manual Entry | All values were entered manually based on published statistics. Where some years were missing, estimates were used for trend continuity. Example: If we had data for: • 2020 Fresh Salary = $2,300<br>• 2022 Fresh Salary = $2,500 Then 2021 was estimated as the midpoint: $2,400. |
| Course Inclusion | All 8 course clusters listed in GES Table 2 were included to ensure complete representation. |
| Standardization | Column names and value formats were aligned to match data.gov.sg conventions (e.g., "Fresh Employment Rate (%)"). |
| Data Validation | Official values from 2021–2023 were directly sourced; earlier years were interpolated based on published trends within each cluster. |
| Formatting | Salaries are in SGD, rates are in %, and all columns are numeric for analysis and visualization. |
3. Data Structure and Calculation¶
The dataset reports:
- No averaging or mean calculations — only official or trend-aligned median salaries were used.
- No mixing of graduate types — fresh and post-NS statistics are stored in separate columns.
- Interpolated values were only used where exact year data was unavailable, and were based on surrounding years.
4. Dataset Columns¶
| Column | Description |
|---|---|
Year |
Graduation year of the surveyed polytechnic cohort |
Course Cluster |
Academic discipline grouping (e.g., Health Sciences, Built Environment) |
Fresh Employment Rate (%) |
% of fresh graduates employed within 6 months after graduation (overall empolyment rate) |
Post-NS Employment Rate (%) |
% of male graduates employed after completing full-time National Service (overall employment rate) |
Fresh Median Salary (SGD) |
Median gross monthly salary of fresh graduates in full-time permanent jobs |
Post-NS Median Salary (SGD) |
Median gross monthly salary of post-NS graduates in full-time permanent jobs |
Notes on Interpretation¶
- Median is used (not mean) to avoid distortion by outliers and align with official GES methodology.
- Employment Rate refers to graduates in the labour force (excluding those pursuing further studies).
- The dataset structure follows the format seen on data.gov.sg and in GES Table 2 releases, enabling valid year-over-year comparisons.
Explanation of Dataset 3: Median Gross Monthly Income by Qualification Level (2019–2024)¶
This dataset, derived from MOM and Labour Force reports, focuses on the median gross monthly income of full-time employed Singapore residents. It includes breakdowns by:
- All Residents
- Degree Holders
- Diploma Holders
This dataset is useful because it shows how much diploma and degree holders earn after working for a few years. It helps us see if uni grads keep earning more over time, or if poly grads catch up — giving a clearer picture beyond just starting salaries.
Sub Question 1: Which education path (polytechnic or university) leads to higher starting salaries and better employment rates from 2016 to 2023?¶
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# Load the dataset
income_df = pd.read_csv('Median_Income_by_Qualification_2019_2024.csv')
# Filter for Degree Holder and Diploma Holder, and years 2021 to 2024 only
filtered = income_df[
(income_df['Qualification Level'].isin(['Degree Holder', 'Diploma Holder'])) &
(income_df['Year'] >= 2021) & (income_df['Year'] <= 2024)
]
plt.figure(figsize=(10,6))
sns.lineplot(
data=filtered,
x='Year',
y='Median Gross Monthly Income (SGD)',
hue='Qualification Level',
marker='o'
)
# Add arrows to indicate the difference for each year
years = sorted(filtered['Year'].unique())
for year in years:
deg = filtered[(filtered['Year'] == year) & (filtered['Qualification Level'] == 'Degree Holder')]['Median Gross Monthly Income (SGD)'].values
dip = filtered[(filtered['Year'] == year) & (filtered['Qualification Level'] == 'Diploma Holder')]['Median Gross Monthly Income (SGD)'].values
if len(deg) > 0 and len(dip) > 0:
plt.annotate(
'',
xy=(year, deg[0]),
xytext=(year, dip[0]),
arrowprops=dict(arrowstyle='<->', color='red', lw=2)
)
# Add text label for the difference
diff = deg[0] - dip[0]
plt.text(year, (deg[0]+dip[0])/2, f'{diff:.0f}', color='red', ha='center', va='center', fontsize=10, fontweight='bold')
plt.title('Median Gross Monthly Income: Degree vs Diploma Holders (2021–2024)')
plt.ylabel('Median Income (SGD)')
plt.xlabel('Year')
plt.legend(title='Qualification Level')
<matplotlib.legend.Legend at 0x1c7ac36efd0>
#We compared the overall employment rate and median salary of polytechnic and university graduates
#Each year may contain multiple clusters or entries (e.g., different diplomas).
#aking the mean gives a single representative value per year for easier comparison in line plots and analysis.
sns.set_theme(style="whitegrid", palette="colorblind", font_scale=1.1)
poly_df = pd.read_csv("Polytechnic_GES_2016_2023_all_clusters.csv")
uni_df = pd.read_csv("Uni_GES.csv")
uni_df['employment_rate_overall'] = pd.to_numeric(uni_df['employment_rate_overall'], errors='coerce')
uni_df['gross_monthly_median'] = pd.to_numeric(uni_df['gross_monthly_median'], errors='coerce')
uni_summary = uni_df.groupby('year').agg({
'employment_rate_overall': 'mean',
'gross_monthly_median': 'mean'
}).reset_index()
poly_summary = poly_df.groupby('Year').agg({
'Fresh Employment Rate (%)': 'mean',
'Post-NS Employment Rate (%)': 'mean',
'Fresh Median Salary (SGD)': 'mean',
'Post-NS Median Salary (SGD)': 'mean'
}).reset_index()
combined = pd.merge(poly_summary, uni_summary, left_on='Year', right_on='year', how='inner')
# --- Employment Rate Plot ---
plt.figure(figsize=(12, 6))
sns.lineplot(data=combined, x='Year', y='Fresh Employment Rate (%)', marker='o', label='Polytechnic (Fresh)')
sns.lineplot(data=combined, x='Year', y='Post-NS Employment Rate (%)', marker='o', label='Polytechnic (Post-NS)')
sns.lineplot(data=combined, x='Year', y='employment_rate_overall', marker='o', label='University')
for i in range(len(combined)):
plt.text(combined['Year'][i], combined['Fresh Employment Rate (%)'][i] + 0.1,
f"{combined['Fresh Employment Rate (%)'][i]:.1f}%", ha='center', fontsize=9)
plt.text(combined['Year'][i], combined['Post-NS Employment Rate (%)'][i] + 0.1,
f"{combined['Post-NS Employment Rate (%)'][i]:.1f}%", ha='center', fontsize=9)
plt.text(combined['Year'][i], combined['employment_rate_overall'][i] + 0.1,
f"{combined['employment_rate_overall'][i]:.1f}%", ha='center', fontsize=9)
plt.title("Overall Employment Rate (2016-2023)", fontsize=16, weight='bold')
plt.ylabel("Employment Rate (%)")
plt.gca().yaxis.set_major_formatter(mtick.PercentFormatter())
plt.xlabel("Year")
plt.legend(title="Education Type", loc='lower right')
plt.tight_layout()
plt.show()
# --- Median Salary Plot ---
plt.figure(figsize=(12, 6))
sns.lineplot(data=combined, x='Year', y='Fresh Median Salary (SGD)', marker='s', label='Polytechnic (Fresh)')
sns.lineplot(data=combined, x='Year', y='Post-NS Median Salary (SGD)', marker='s', label='Polytechnic (Post-NS)')
sns.lineplot(data=combined, x='Year', y='gross_monthly_median', marker='s', label='University')
for i in range(len(combined)):
plt.text(combined['Year'][i], combined['Fresh Median Salary (SGD)'][i] + 20,
f"${combined['Fresh Median Salary (SGD)'][i]:.0f}", ha='center', fontsize=9)
plt.text(combined['Year'][i], combined['Post-NS Median Salary (SGD)'][i] + 20,
f"${combined['Post-NS Median Salary (SGD)'][i]:.0f}", ha='center', fontsize=9)
plt.text(combined['Year'][i], combined['gross_monthly_median'][i] + 20,
f"${combined['gross_monthly_median'][i]:.0f}", ha='center', fontsize=9)
plt.title("Median Starting Salary (2016-2023)", fontsize=16, weight='bold')
plt.ylabel("Median Salary (SGD)")
plt.gca().yaxis.set_major_formatter(mtick.StrMethodFormatter('${x:,.0f}'))
plt.xlabel("Year")
plt.legend(title="Education Type", loc='upper left')
plt.tight_layout()
plt.show()
Comments on Graphs Used in Sub Question 1¶
1. Median Gross Monthly Income: Degree vs Diploma Holders (2021–2024)
- This line graph clearly shows the income gap between degree and diploma holders over four years.
- The arrows and difference labels highlight how much more degree holders earn compared to diploma holders each year.
- The upward trend for both groups suggests rising incomes, but the gap remains substantial, emphasizing the financial advantage of a university degree in the short term.
2. Overall Employment Rate (2016–2023)
- This line graph compares employment rates for polytechnic (fresh and post-NS) and university graduates.
- All groups show high employment rates, with university graduates generally having a slight edge.
- The graph helps visualize stability and small fluctuations in employment outcomes across years and education types.
3. Median Starting Salary (2016–2023)
- This graph compares median starting salaries for polytechnic (fresh and post-NS) and university graduates.
- University graduates consistently earn higher starting salaries than polytechnic graduates.
- The salary gap is visually apparent, supporting the narrative that university education leads to higher initial earnings.
Overall Linkage Statement¶
The graphs in Sub Question 1 provide a comprehensive comparison of starting salaries and employment rates for polytechnic and university graduates in Singapore. They show that university graduates enjoy higher starting salaries and slightly better employment rates, while polytechnic graduates also achieve strong employment outcomes.
Importantly, the difference between median gross monthly income for degree and diploma holders (see the first line graph) is much more noticeable and larger compared to the difference in starting salaries for polytechnic and university graduates (see the third line graph). This suggests that as graduates gain more work experience, the income gap between degree and diploma holders widens, even though their starting salaries are closer at the beginning.
Together, these visualizations directly address the topic statement by illustrating both the short-term and long-term financial and employment advantages associated with each education path, helping students make informed decisions about their future.
Sub question 2: If I want to live comfortably in Singapore (in 2023), which course clusters offer the best starting salaries — and is it better to pursue them through poly or uni?¶
Definition of living comfortably¶
From 2016 to 2024, the median gross monthly household income per household member in Singapore rose from about S$2,699 to S$3,615. Using a comfort benchmark of 1.0–1.4 times the median—enough to cover needs, leisure, and savings—this means living comfortably would require about S$2,700 to S$5,000 per person each month, with the lower end in 2016 and the higher end in 2024. Averaging the medians over this period (S$3,111) and applying a midpoint multiplier of 1.2× gives a single benchmark figure of S$3,733 gross monthly income per household member for living comfortably in Singapore during 2016–2024.
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# Load datasets
poly_df = pd.read_csv("Polytechnic_GES_2016_2023_all_clusters.csv")
uni_df = pd.read_csv("Uni_GES.csv")
# Prepare data
poly_df['Year'] = poly_df['Year'].astype(int)
uni_df['year'] = uni_df['year'].astype(int)
uni_df['gross_monthly_median'] = pd.to_numeric(uni_df['gross_monthly_median'], errors='coerce')
def categorize_degree(row):
degree = row['degree'].lower()
if 'engineering' in degree:
return 'Engineering'
elif 'science' in degree or 'chemistry' in degree or 'biology' in degree or 'physics' in degree:
return 'Science'
elif 'comput' in degree or 'infocomm' in degree or 'information' in degree or 'data' in degree or 'it' in degree:
return 'IT'
elif 'business' in degree or 'accountancy' in degree or 'finance' in degree or 'economics' in degree:
return 'Business'
elif ('english' in degree or 'literature' in degree or 'arts' in degree or 'media' in degree or
'communication' in degree or 'music' in degree or 'design' in degree):
return 'Arts & Humanities'
elif 'health' in degree or 'medicine' in degree or 'nursing' in degree or 'pharmacy' in degree:
return 'Health Sciences'
elif 'architecture' in degree or 'built environment' in degree:
return 'Built Environment'
elif 'law' in degree or 'legal' in degree:
return 'Law'
elif 'education' in degree or 'teaching' in degree:
return 'Education'
elif 'social work' in degree or 'social science' in degree or 'psychology' in degree:
return 'Social Sciences'
else:
return 'Arts & Humanities'
uni_df['Degree Group'] = uni_df.apply(categorize_degree, axis=1)
# Fill missing gross_monthly_median with median of Degree Group and year
uni_df['gross_monthly_median'] = uni_df.groupby(['Degree Group', 'year'])['gross_monthly_median']\
.transform(lambda x: x.fillna(x.median()))
# If still missing, fill with overall median for that year
uni_df['gross_monthly_median'] = uni_df.groupby('year')['gross_monthly_median']\
.transform(lambda x: x.fillna(x.median()))
# Optionally, drop any remaining missing values
uni_df = uni_df.dropna(subset=['gross_monthly_median'])
# Years to plot
years = sorted(set(poly_df['Year']).intersection(set(uni_df['year'])))
# Comfortable salary benchmark
COMFORTABLE_SALARY = 3733 # S$3,733 as per your definition
# Find min/max salary for all years to set a focused y-axis range
min_salary = min(poly_df['Fresh Median Salary (SGD)'].min(), uni_df['gross_monthly_median'].min())
max_salary = max(poly_df['Fresh Median Salary (SGD)'].max(), uni_df['gross_monthly_median'].max())
ymin = min(min_salary - 100, COMFORTABLE_SALARY - 200)
ymax = max(max_salary + 100, COMFORTABLE_SALARY + 200)
# Create subplots: 2 columns (Poly, Uni), rows = number of years
fig, axes = plt.subplots(len(years), 2, figsize=(14, 4 * len(years)), sharex=False, sharey=True)
for idx, yr in enumerate(years):
# Polytechnic subplot
poly_year = poly_df[poly_df['Year'] == yr]
sns.barplot(
data=poly_year,
x='Fresh Median Salary (SGD)',
y='Course Cluster',
palette='crest',
ax=axes[idx, 0]
)
axes[idx, 0].set_title(f'Polytechnic ({yr})')
axes[idx, 0].set_xlabel('Median Salary (SGD)')
axes[idx, 0].set_ylabel('Course Cluster')
axes[idx, 0].set_xlim(ymin, ymax)
axes[idx, 0].axvline(COMFORTABLE_SALARY, color='blue', linestyle='--', label='Comfortable Salary (S$3,733)')
# Calculate and plot top 10% salary line for poly for this year
top10_salary = poly_year['Fresh Median Salary (SGD)'].quantile(0.9)
axes[idx, 0].axvline(top10_salary, color='red', linestyle=':', label='Top 10% Poly Salary')
# Show legend only once per subplot
handles, labels = axes[idx, 0].get_legend_handles_labels()
by_label = dict(zip(labels, handles))
axes[idx, 0].legend(by_label.values(), by_label.keys(), loc='lower right')
# University subplot
uni_year = uni_df[uni_df['year'] == yr]
sns.barplot(
data=uni_year,
x='gross_monthly_median',
y='Degree Group',
palette='flare',
ax=axes[idx, 1]
)
axes[idx, 1].set_title(f'University ({yr})')
axes[idx, 1].set_xlabel('Median Salary (SGD)')
axes[idx, 1].set_ylabel('Degree Group')
axes[idx, 1].set_xlim(ymin, ymax)
axes[idx, 1].axvline(COMFORTABLE_SALARY, color='blue', linestyle='--', label='Comfortable Salary (S$3,733)')
axes[idx, 1].legend(loc='lower right')
plt.tight_layout()
plt.show()
c:\Users\User\AppData\Local\Programs\Python\Python313\Lib\site-packages\numpy\lib\_nanfunctions_impl.py:1215: RuntimeWarning: Mean of empty slice return np.nanmean(a, axis, out=out, keepdims=keepdims) c:\Users\User\AppData\Local\Programs\Python\Python313\Lib\site-packages\numpy\lib\_nanfunctions_impl.py:1215: RuntimeWarning: Mean of empty slice return np.nanmean(a, axis, out=out, keepdims=keepdims) C:\Users\User\AppData\Local\Temp\ipykernel_12792\2481880264.py:69: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `y` variable to `hue` and set `legend=False` for the same effect. sns.barplot( C:\Users\User\AppData\Local\Temp\ipykernel_12792\2481880264.py:93: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `y` variable to `hue` and set `legend=False` for the same effect. sns.barplot( C:\Users\User\AppData\Local\Temp\ipykernel_12792\2481880264.py:69: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `y` variable to `hue` and set `legend=False` for the same effect. sns.barplot( C:\Users\User\AppData\Local\Temp\ipykernel_12792\2481880264.py:93: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `y` variable to `hue` and set `legend=False` for the same effect. sns.barplot( C:\Users\User\AppData\Local\Temp\ipykernel_12792\2481880264.py:69: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `y` variable to `hue` and set `legend=False` for the same effect. sns.barplot( C:\Users\User\AppData\Local\Temp\ipykernel_12792\2481880264.py:93: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `y` variable to `hue` and set `legend=False` for the same effect. sns.barplot( C:\Users\User\AppData\Local\Temp\ipykernel_12792\2481880264.py:69: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `y` variable to `hue` and set `legend=False` for the same effect. sns.barplot( C:\Users\User\AppData\Local\Temp\ipykernel_12792\2481880264.py:93: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `y` variable to `hue` and set `legend=False` for the same effect. sns.barplot( C:\Users\User\AppData\Local\Temp\ipykernel_12792\2481880264.py:69: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `y` variable to `hue` and set `legend=False` for the same effect. sns.barplot( C:\Users\User\AppData\Local\Temp\ipykernel_12792\2481880264.py:93: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `y` variable to `hue` and set `legend=False` for the same effect. sns.barplot( C:\Users\User\AppData\Local\Temp\ipykernel_12792\2481880264.py:69: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `y` variable to `hue` and set `legend=False` for the same effect. sns.barplot( C:\Users\User\AppData\Local\Temp\ipykernel_12792\2481880264.py:93: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `y` variable to `hue` and set `legend=False` for the same effect. sns.barplot( C:\Users\User\AppData\Local\Temp\ipykernel_12792\2481880264.py:69: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `y` variable to `hue` and set `legend=False` for the same effect. sns.barplot( C:\Users\User\AppData\Local\Temp\ipykernel_12792\2481880264.py:93: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `y` variable to `hue` and set `legend=False` for the same effect. sns.barplot( C:\Users\User\AppData\Local\Temp\ipykernel_12792\2481880264.py:69: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `y` variable to `hue` and set `legend=False` for the same effect. sns.barplot( C:\Users\User\AppData\Local\Temp\ipykernel_12792\2481880264.py:93: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `y` variable to `hue` and set `legend=False` for the same effect. sns.barplot(
# Load data
poly_df = pd.read_csv("Polytechnic_GES_2016_2023_all_clusters.csv")
uni_df = pd.read_csv("Uni_GES.csv")
# Extract all fresh median salaries for polytechnic (all years, all clusters)
poly_salaries = poly_df['Fresh Median Salary (SGD)'].dropna().values
# Extract all university gross monthly median salaries (all years, all degrees)
uni_df['gross_monthly_median'] = pd.to_numeric(uni_df['gross_monthly_median'], errors='coerce')
uni_salaries = uni_df['gross_monthly_median'].dropna().values
# Sort for CDF
poly_salaries_sorted = np.sort(poly_salaries)
uni_salaries_sorted = np.sort(uni_salaries)
# Define colors for each percentile
percentile_colors = {25: 'red', 50: 'orange', 75: 'purple'}
plt.figure(figsize=(8, 6))
# Plot CDF using step plot
plt.step(poly_salaries_sorted, np.linspace(0, 1, len(poly_salaries_sorted)), where='post', label='Polytechnic', color='blue')
plt.step(uni_salaries_sorted, np.linspace(0, 1, len(uni_salaries_sorted)), where='post', label='University', color='green')
# Mark key percentiles with different colors
for df, label, base_color in zip([poly_salaries_sorted, uni_salaries_sorted], ['Polytechnic', 'University'], ['blue', 'green']):
for p in [25, 50, 75]:
percentile_value = np.percentile(df, p)
plt.axvline(percentile_value, linestyle='--', color=percentile_colors[p], label=f'{label} {p}th Percentile')
plt.xlabel('Median Salary (SGD)')
plt.ylabel('Cumulative Distribution (CDF)')
plt.title('Cumulative Distribution with Percentiles: Polytechnic vs University')
plt.legend()
plt.grid(True)
Comments on Bar Plots¶
- These barplots allow a direct comparison of starting salaries across different fields for both polytechnic and university graduates from 2016 to 2023.
- The addition of the “comfortable salary” line (S$3,733) provides a clear benchmark, making it easy to see which clusters or degree groups meet or exceed the threshold for living comfortably in Singapore.
- The top 10% salary line for polytechnic graduates highlights the highest-earning clusters, showing that only a few polytechnic disciplines consistently approach or surpass the comfortable salary benchmark.
- For university graduates, more degree groups tend to meet or exceed the comfortable salary line, especially in fields like IT, Engineering, and Health Sciences.
- The year-by-year breakdown reveals how salary outcomes have shifted over time, with some clusters showing steady growth and others remaining relatively flat.
Comments on the CDF Graph:¶
- The CDF (Cumulative Distribution Function) graph compares the spread of starting salaries for polytechnic (blue) and university (green) graduates.
- The blue curve (polytechnic) rises steeply and is concentrated at lower salary values, showing that most polytechnic graduates have starting salaries within a narrow, lower range.
- The green curve (university) is stretched further to the right, indicating a wider spread and generally higher starting salaries for university graduates.
- The vertical dashed lines mark the 25th, 50th (median), and 75th percentiles for each group:
- For polytechnic, all percentiles are clustered between about $2,000 and $2,800.
- For university, the percentiles are much higher, with the 75th percentile above $4,000.
- This means a much larger proportion of university graduates earn higher starting salaries compared to polytechnic graduates.
- The gap between the two curves at each percentile highlights the salary advantage of university graduates, especially for those in the top 25%.
- Overall, the graph visually demonstrates that university graduates are more likely to be among the high earners, while polytechnic graduates are concentrated in the lower salary brackets.
Overall Linkage Statement¶
The combination of barplots and CDF plots in Sub Question 2 offers a comprehensive comparison of salary outcomes by field and education path. The barplots make it easy to identify which specific clusters or degree groups offer the best starting salaries and whether they meet the benchmark for living comfortably in Singapore. The CDF plot complements this by showing the overall distribution of salaries, highlighting the broader advantage university graduates have in terms of higher starting pay.
Interesting Trends and Insights¶
- Consistent High Earners: University graduates in IT, Engineering, and Health Sciences consistently exceed the comfortable salary benchmark, while only a few polytechnic clusters (such as IT or Engineering) occasionally reach this level.
- Salary Gaps: The gap between polytechnic and university starting salaries is visually apparent, especially in high-demand fields.
- Top Performers: The top 10% of polytechnic graduates can achieve salaries close to or above the comfortable benchmark, but this is not the norm for most clusters.
- Yearly Progression: Over the years, there is a gradual upward trend in starting salaries for both education paths, but the rate of increase and the ability to surpass the comfortable salary line varies by field and qualification.
- University Salary Advantage Across the Board: The CDF graph shows that even the lowest-earning university graduates (25th percentile) tend to earn more than the median polytechnic graduate, highlighting a consistent and significant salary advantage for
Direct Answer to Sub Question 2¶
If your goal is to live comfortably in Singapore right after graduation, pursuing a university degree—especially in IT, Engineering, Health Sciences, or Business—offers the best chance of achieving a starting salary above the comfortable living benchmark of S$3,733. Polytechnic graduates can also achieve this, but typically only those in the top-earning clusters or top 10% of their cohort. For most students, the university path provides a clearer and more reliable route to a comfortable starting income, while polytechnic graduates generally have lower starting salaries with fewer clusters meeting the comfort threshold. The choice of field is crucial, as some university degree groups in non-technical areas may have starting salaries closer to polytechnic graduates, but overall, university graduates have a significant advantage in achieving financial comfort upon entering the workforce.
Options based on industry¶
If you want to pursue a STEM industry (such as engineering, science, or technology) and aim to meet the comfortable salary benchmark (S$3,733/month), a university degree in Health Sciences is the best option, as graduates consistently exceed this salary benchmark by a large margin. However, there is a sharp decrease in 2023, which may be worth noting, though salaries still remain above the comfortable benchmark. For a more stable and uniform salary growth, a degree in IT or Engineering is more recommended, as graduates in these fields show consistent increases across the years. Polytechnic graduates in IT or related fields may occasionally approach the benchmark, but this is usually limited to the top-performing clusters or the top 10% of earners.
If you are not aiming for a STEM industry, a university degree in Law would be strongly recommended. Law graduates have consistently earned high starting salaries, and in 2023, the median starting salary exceeded the comfortable benchmark by more than double. This makes Law a reliable choice for students seeking a high-paying career outside of STEM fields.
SUB QUESTION 3: Is it worth going to polytechnic or university in Singapore in 2023 when comparing starting salaries to course fees using ROI?¶
# Load your fees and salary data
fees_df = pd.read_csv('course_fees_overall.csv')
poly_df = pd.read_csv('Polytechnic_GES_2016_2023_all_clusters.csv')
uni_df = pd.read_csv('Uni_GES.csv')
# Prepare salary data (example, adjust as needed)
poly_2023 = poly_df[poly_df['Year'] == 2023]
uni_2023 = uni_df[uni_df['year'] == 2023].copy()
poly_median_salary = poly_2023['Fresh Median Salary (SGD)'].median()
poly_institutions = fees_df[fees_df['Type'] == 'Diploma']['Institution'].unique()
poly_salary_df = pd.DataFrame({
'Institution': poly_institutions,
'Median_Salary': poly_median_salary
})
uni_2023['gross_monthly_median'] = pd.to_numeric(uni_2023['gross_monthly_median'], errors='coerce')
uni_median_salary = uni_2023.groupby('university')['gross_monthly_median'].median().reset_index()
uni_median_salary = uni_median_salary.rename(columns={'university': 'Institution', 'gross_monthly_median': 'Median_Salary'})
salary_df = pd.concat([poly_salary_df, uni_median_salary], ignore_index=True)
# Merge with fees
merged = pd.merge(fees_df, salary_df, on='Institution')
# --- Calculate ROI for each Institution and Student Type ---
merged['ROI'] = merged['Median_Salary'] / merged['Total_Cost']
# --- Add Education Level column ---
merged['Education Level'] = merged['Type'].apply(lambda x: 'Polytechnic' if x == 'Diploma' else 'University')
# --- Visualize ROI by Institution and Student Type (Bar Chart) ---
plt.figure(figsize=(14, 8))
ax = sns.barplot(data=merged, x='Institution', y='ROI', hue='Student_Type', palette=['tab:blue', 'tab:orange', 'tab:green'])
plt.xticks(rotation=45, ha='right')
plt.title('ROI by Institution and Student Type (2023)')
plt.ylabel('ROI (Median Salary / Total Tuition Fee)')
plt.xlabel('Institution')
# Find highest ROI for each Student_Type and add arrow (indicator) with matching color
arrow_handles = []
arrow_labels = []
for stype, color in zip(['SC', 'SPR', 'IS'], ['tab:blue', 'tab:orange', 'tab:green']):
highest = merged[merged['Student_Type'] == stype].sort_values('ROI', ascending=False).iloc[0]
xpos = list(merged['Institution'].unique()).index(highest['Institution'])
plt.annotate(
f'Highest ROI ({stype})',
xy=(xpos, highest['ROI']),
xytext=(xpos, highest['ROI'] + 0.05),
arrowprops=dict(facecolor=color, shrink=0.05, width=2, headwidth=8),
ha='center',
fontsize=10,
color=color
)
# Add colored arrow to legend
arrow_handles.append(Line2D([0], [0], color=color, marker=r'$\uparrow$', linestyle='None', markersize=12))
arrow_labels.append(f'Arrow: Highest ROI for {stype}')
# Add custom legend entries for arrows
from matplotlib.lines import Line2D
handles, labels = ax.get_legend_handles_labels()
plt.legend(handles + arrow_handles, labels + arrow_labels, title='Student Type')
plt.tight_layout()
plt.show()
# --- Visualize ROI Distribution by Education Level and Student Type (Boxplot) ---
plt.figure(figsize=(10, 6))
sns.boxplot(data=merged, x='Education Level', y='ROI', hue='Student_Type', palette=['tab:blue', 'tab:orange', 'tab:green'])
plt.title('ROI Distribution by Education Level and Student Type (2023)')
plt.ylabel('ROI (Median Salary / Total Tuition Fee)')
plt.xlabel('Education Level')
plt.legend(title='Student Type')
<matplotlib.legend.Legend at 0x1c7b3cf4050>
Note¶
Assume standard course durations: 3 years for diplomas and an average of 3.5 years for bachelor’s degrees (to account for variation between universities).
Tuition fees considered exclude subsidies; only school fees are included for both polytechnic and university students.
ROI Definition¶
Return on Investment (ROI) compares the median starting salary of graduates to the total tuition fees paid. A higher ROI indicates better immediate financial return per dollar spent on education.
Comments on Graphs Used in Sub Question 3¶
1. ROI Bar Chart (by Institution and Student Type)
- This bar chart compares the financial return of polytechnic and university education in Singapore for 2023, segmented by institution and student type (Singapore Citizen, Permanent Resident, International Student).
- Polytechnic graduates, especially Singapore Citizens, achieve a much higher ROI due to lower tuition fees and competitive starting salaries.
- University graduates have lower ROI across all student types, mainly because of higher tuition costs.
- However NTU university graduates have the highest ROI for IS students, these are some possible reasons why:
- Lower Tuition Fees for IS at NTU: NTU may offer more competitive tuition rates for international students compared to other universities, reducing the total cost and boosting ROI.
- High Median Starting Salaries: NTU graduates, especially in high-demand fields like engineering, computing, or business, may command higher starting salaries, increasing ROI.
- Strong Industry Connections: NTU’s reputation and partnerships with employers could lead to better job placements and higher initial pay for graduates.
- Popular High-Paying Courses: Many international students at NTU may be enrolled in disciplines with strong salary outcomes (e.g., Computer Science, Engineering).
- Efficient Course Duration: NTU’s programs may allow students to graduate faster or with less time spent, reducing overall costs and improving ROI.
- Scholarships and Financial Aid: NTU might provide more scholarships or financial support for international students, effectively lowering their net tuition fees.
- The chart highlights that polytechnic education offers better short-term financial value per dollar spent, with the highest ROI seen among polytechnic Singapore Citizens.
2. ROI Distribution Boxplot (by Education Level and Student Type)
- This boxplot visualizes the spread of ROI values for polytechnic and university graduates, grouped by education level and student type.
- Polytechnic graduates consistently achieve higher ROI, with Singapore Citizens showing the greatest returns.
- University ROI is lower and more variable, especially for International Students who pay the highest fees.
- The boxplot emphasizes the strong financial advantage of polytechnic education in terms of immediate ROI, while also illustrating the impact of student type on educational value.
Polytechnic Details:
- SC poly ROI shows large variability (tall box), suggesting big differences across courses or institutions—some programs return tuition costs very quickly, while others take much longer.
- SPR and IS poly groups have almost no variability (thin box, short whiskers), meaning their ROI is consistently low across all programs. This suggests tuition fees are high enough that salary differences barely shift ROI.
University Details:
- All three student types show moderate variability, with SC uni ROI having a slightly wider spread.
- This could be due to course-specific salary differences (e.g., computing vs arts) impacting ROI more significantly when tuition is similar within a student type.
- There’s a small outlier in IS uni, indicating an unusually high ROI case—likely a high-paying discipline.
Overall Linkage Statement¶
The ROI analysis directly addresses the topic by comparing polytechnic and university graduates in Singapore not only in terms of starting salaries and employment rates but also the financial value of their education. The results show that polytechnic graduates, particularly Singapore Citizens, enjoy a higher immediate ROI due to lower tuition fees coupled with competitive starting salaries. Conversely, university graduates tend to have a lower short-term ROI but may benefit from greater long-term earning potential. This comprehensive comparison helps students make informed decisions about which education path offers better financial outcomes in Singapore from 2016 to 2023.
SUMMARY¶
This analysis shows that from 2016 to 2023, university graduates in Singapore consistently have higher starting salaries and slightly better employment rates than polytechnic graduates, especially in high-demand fields like IT, Engineering, and Health Sciences. While polytechnic graduates enjoy strong job prospects and a much higher short-term return on investment due to lower tuition fees, the income gap between degree and diploma holders widens over time as university graduates see faster salary growth. Overall, university education offers greater long-term financial benefits and earning potential, while polytechnic education provides better immediate value for money and quicker workforce entry. The best path depends on individual goals, field of study, and whether one prioritizes short-term ROI or long-term income growth.
REFERENCES¶
Ministry of Manpower. (2024). Infographic on Local Employment Outcomes. Available at: https://stats.mom.gov.sg/iMAS_Infographics/mrsd-infographic-local_emp_outcomes.pdf [Accessed 12 Aug. 2025].
Ministry of Manpower. (2024). Summary Table: Income. Available at: https://stats.mom.gov.sg/Pages/Income-Summary-Table.aspx [Accessed 12 Aug. 2025].
Channel NewsAsia. (2024). Median monthly household income exceeds S$11,000 in 2024, a 1.4% rise after adjusting for inflation. Available at: https://www.channelnewsasia.com/singapore/median-monthly-household-income-real-after-inflation-household-singstat-4934851 [Accessed 12 Aug. 2025].
The Straits Times. (2024). Household incomes rise in 2024; resident households received more support from government schemes. Available at: https://www.straitstimes.com/singapore/household-incomes-rise-in-2024-resident-households-received-more-support-from-government-schemes [Accessed 12 Aug. 2025].
DollarsAndSense. (2024). Singapore’s Average Household Income: How Different Salaries Compare. Available at: https://dollarsandsense.sg/singapores-average-household-income-different-salaries-earn/ [Accessed 12 Aug. 2025].
Ministry of Education. (2023). Education Statistics Digest 2023. Available at: https://www.moe.gov.sg/-/media/files/about-us/education-statistics-digest-2023.pdf [Accessed 12 Aug. 2025].
National University of Singapore. (2025). Course Fees. Available at: https://www.nus.edu.sg/oam/search-result?q=course%20fees [Accessed 12 Aug. 2025].
Singapore Polytechnic. (2025). Full-Time Diploma Course Fees. Available at: https://www.sp.edu.sg/admissions/course-fees/full-time-diploma [Accessed 12 Aug. 2025].
Ngee Ann Polytechnic. (2025). Course Fees. Available at: https://www.np.edu.sg/admissions-enrolment/academic-matters/course-fees [Accessed 12 Aug. 2025].
Nanyang Polytechnic. (2025). Annual Course Fees. Available at: https://www.nyp.edu.sg/student/study/scholarships-financial-matters/fees/annual-course-fees [Accessed 12 Aug. 2025].
Republic Polytechnic. (2025). Financial Matters. Available at: https://www.rp.edu.sg/financial-matters [Accessed 12 Aug. 2025].
Temasek Polytechnic. (2025). Part-Time Post-Diploma Application Guide. Available at: https://www.tp.edu.sg/admissions-and-finance/application-guide-for-adult-learners/part-time-post-diploma-application-guide.html [Accessed 12 Aug. 2025].
Nanyang Technological University. (2025). Tuition Fees. Available at: https://www.ntu.edu.sg/admissions/undergraduate/financial-matters/tuition-fees [Accessed 12 Aug. 2025].
Singapore University of Social Sciences. (2025). Tuition Fee Subsidy for Full-Time Undergraduates. Available at: https://www.suss.edu.sg/admissions/financial-matters/tuition-fee-subsidy/full-time-undergraduate [Accessed 12 Aug. 2025].
Singapore University of Technology and Design. (2025). Tuition Fees. Available at: https://www.sutd.edu.sg/admissions/undergraduate/education-expenses/fees/tuition-fees/ [Accessed 12 Aug. 2025].
Singapore Management University. (2025). Tuition Fees. Available at: https://www.smu.edu.sg/campus-life/financial-matters/tuition-fees [Accessed 12 Aug. 2025].
Government Technology Agency. (2025). Data.gov.sg: Singapore’s open data portal. Available at: https://data.gov.sg [Accessed 12 Aug. 2025].