Clean And Analyze Employee Exit Surveys

Introduction

In this project, I am going work with exit surveys from employees of the Department of Education, Training and Employment) (DETE) and the Technical and Further Education (TAFE) institute in Queensland, Australia.

Datasets

You can find two exit survey datasets from following links. However, We've made some slight modifications to these datasets to make them easier to work with.

Objective

Our main objective is to find the answers for following questions:

  • Are employees who only worked for the institutes for a short period of time resigning due to some kind of dissatisfaction? What about employees who have been there longer?
  • Are younger employees resigning due to some kind of dissatisfaction? What about older employees?

The approach to reach the goal

Following steps will be followed to reach the goal:

  • Explore the data and figure out how to prepare it for analysis
  • Clean the data
  • Combine two data sets
  • Handle the missing values
  • Aggregate the data
  • Analayze data and answer the questions

Results found

Findings of my analysis are as below:

  • The established employees are more likely to resign due to dissatisfaction while the new employees are the least resigned due to dissatisfaction.
  • Older employees are more prone to resign due to dissatisfaction, while younger employees are generally less likely to resign due to dissatisfaction.

Importing libraries

In [1]:
# Import all required modules for the project
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
In [2]:
# Make cell output scroll horizontally
from IPython.core.display import HTML
display(HTML("<style>pre { white-space: pre !important; }</style>"))

Reading the datasets

In [3]:
# Reading two datasets
dete_survey = pd.read_csv('dete_survey.csv')
tafe_survey = pd.read_csv('tafe_survey.csv')

Exploring the dataframes

In [4]:
# Initial exploration of DETE survey dataset
dete_survey.info()
dete_survey.head()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 822 entries, 0 to 821
Data columns (total 56 columns):
 #   Column                               Non-Null Count  Dtype 
---  ------                               --------------  ----- 
 0   ID                                   822 non-null    int64 
 1   SeparationType                       822 non-null    object
 2   Cease Date                           822 non-null    object
 3   DETE Start Date                      822 non-null    object
 4   Role Start Date                      822 non-null    object
 5   Position                             817 non-null    object
 6   Classification                       455 non-null    object
 7   Region                               822 non-null    object
 8   Business Unit                        126 non-null    object
 9   Employment Status                    817 non-null    object
 10  Career move to public sector         822 non-null    bool  
 11  Career move to private sector        822 non-null    bool  
 12  Interpersonal conflicts              822 non-null    bool  
 13  Job dissatisfaction                  822 non-null    bool  
 14  Dissatisfaction with the department  822 non-null    bool  
 15  Physical work environment            822 non-null    bool  
 16  Lack of recognition                  822 non-null    bool  
 17  Lack of job security                 822 non-null    bool  
 18  Work location                        822 non-null    bool  
 19  Employment conditions                822 non-null    bool  
 20  Maternity/family                     822 non-null    bool  
 21  Relocation                           822 non-null    bool  
 22  Study/Travel                         822 non-null    bool  
 23  Ill Health                           822 non-null    bool  
 24  Traumatic incident                   822 non-null    bool  
 25  Work life balance                    822 non-null    bool  
 26  Workload                             822 non-null    bool  
 27  None of the above                    822 non-null    bool  
 28  Professional Development             808 non-null    object
 29  Opportunities for promotion          735 non-null    object
 30  Staff morale                         816 non-null    object
 31  Workplace issue                      788 non-null    object
 32  Physical environment                 817 non-null    object
 33  Worklife balance                     815 non-null    object
 34  Stress and pressure support          810 non-null    object
 35  Performance of supervisor            813 non-null    object
 36  Peer support                         812 non-null    object
 37  Initiative                           813 non-null    object
 38  Skills                               811 non-null    object
 39  Coach                                767 non-null    object
 40  Career Aspirations                   746 non-null    object
 41  Feedback                             792 non-null    object
 42  Further PD                           768 non-null    object
 43  Communication                        814 non-null    object
 44  My say                               812 non-null    object
 45  Information                          816 non-null    object
 46  Kept informed                        813 non-null    object
 47  Wellness programs                    766 non-null    object
 48  Health & Safety                      793 non-null    object
 49  Gender                               798 non-null    object
 50  Age                                  811 non-null    object
 51  Aboriginal                           16 non-null     object
 52  Torres Strait                        3 non-null      object
 53  South Sea                            7 non-null      object
 54  Disability                           23 non-null     object
 55  NESB                                 32 non-null     object
dtypes: bool(18), int64(1), object(37)
memory usage: 258.6+ KB
Out[4]:
ID SeparationType Cease Date DETE Start Date Role Start Date Position Classification Region Business Unit Employment Status ... Kept informed Wellness programs Health & Safety Gender Age Aboriginal Torres Strait South Sea Disability NESB
0 1 Ill Health Retirement 08/2012 1984 2004 Public Servant A01-A04 Central Office Corporate Strategy and Peformance Permanent Full-time ... N N N Male 56-60 NaN NaN NaN NaN Yes
1 2 Voluntary Early Retirement (VER) 08/2012 Not Stated Not Stated Public Servant AO5-AO7 Central Office Corporate Strategy and Peformance Permanent Full-time ... N N N Male 56-60 NaN NaN NaN NaN NaN
2 3 Voluntary Early Retirement (VER) 05/2012 2011 2011 Schools Officer NaN Central Office Education Queensland Permanent Full-time ... N N N Male 61 or older NaN NaN NaN NaN NaN
3 4 Resignation-Other reasons 05/2012 2005 2006 Teacher Primary Central Queensland NaN Permanent Full-time ... A N A Female 36-40 NaN NaN NaN NaN NaN
4 5 Age Retirement 05/2012 1970 1989 Head of Curriculum/Head of Special Education NaN South East NaN Permanent Full-time ... N A M Female 61 or older NaN NaN NaN NaN NaN

5 rows × 56 columns

In [5]:
# Exploring No. of missing values in each column of DETE Survey Dataset
print('No. of missing values in each column of DETE Survey Dataset')
print('------------------------------------------------------------')
dete_survey.isnull().sum()
No. of missing values in each column of DETE Survey Dataset
------------------------------------------------------------
Out[5]:
ID                                       0
SeparationType                           0
Cease Date                               0
DETE Start Date                          0
Role Start Date                          0
Position                                 5
Classification                         367
Region                                   0
Business Unit                          696
Employment Status                        5
Career move to public sector             0
Career move to private sector            0
Interpersonal conflicts                  0
Job dissatisfaction                      0
Dissatisfaction with the department      0
Physical work environment                0
Lack of recognition                      0
Lack of job security                     0
Work location                            0
Employment conditions                    0
Maternity/family                         0
Relocation                               0
Study/Travel                             0
Ill Health                               0
Traumatic incident                       0
Work life balance                        0
Workload                                 0
None of the above                        0
Professional Development                14
Opportunities for promotion             87
Staff morale                             6
Workplace issue                         34
Physical environment                     5
Worklife balance                         7
Stress and pressure support             12
Performance of supervisor                9
Peer support                            10
Initiative                               9
Skills                                  11
Coach                                   55
Career Aspirations                      76
Feedback                                30
Further PD                              54
Communication                            8
My say                                  10
Information                              6
Kept informed                            9
Wellness programs                       56
Health & Safety                         29
Gender                                  24
Age                                     11
Aboriginal                             806
Torres Strait                          819
South Sea                              815
Disability                             799
NESB                                   790
dtype: int64
In [6]:
# Grouping null value percentages in DETE survey dataset 
(dete_survey.isnull().sum()/822).value_counts(normalize = True, bins = 10).sort_index(ascending = True)
Out[6]:
(-0.001996, 0.0996]    0.857143
(0.0996, 0.199]        0.017857
(0.199, 0.299]         0.000000
(0.299, 0.399]         0.000000
(0.399, 0.498]         0.017857
(0.498, 0.598]         0.000000
(0.598, 0.697]         0.000000
(0.697, 0.797]         0.000000
(0.797, 0.897]         0.017857
(0.897, 0.996]         0.089286
dtype: float64
In [7]:
# Initial exploration of TAFE survey data set
tafe_survey.info()
tafe_survey.head()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 702 entries, 0 to 701
Data columns (total 72 columns):
 #   Column                                                                                                                                                         Non-Null Count  Dtype  
---  ------                                                                                                                                                         --------------  -----  
 0   Record ID                                                                                                                                                      702 non-null    float64
 1   Institute                                                                                                                                                      702 non-null    object 
 2   WorkArea                                                                                                                                                       702 non-null    object 
 3   CESSATION YEAR                                                                                                                                                 695 non-null    float64
 4   Reason for ceasing employment                                                                                                                                  701 non-null    object 
 5   Contributing Factors. Career Move - Public Sector                                                                                                              437 non-null    object 
 6   Contributing Factors. Career Move - Private Sector                                                                                                             437 non-null    object 
 7   Contributing Factors. Career Move - Self-employment                                                                                                            437 non-null    object 
 8   Contributing Factors. Ill Health                                                                                                                               437 non-null    object 
 9   Contributing Factors. Maternity/Family                                                                                                                         437 non-null    object 
 10  Contributing Factors. Dissatisfaction                                                                                                                          437 non-null    object 
 11  Contributing Factors. Job Dissatisfaction                                                                                                                      437 non-null    object 
 12  Contributing Factors. Interpersonal Conflict                                                                                                                   437 non-null    object 
 13  Contributing Factors. Study                                                                                                                                    437 non-null    object 
 14  Contributing Factors. Travel                                                                                                                                   437 non-null    object 
 15  Contributing Factors. Other                                                                                                                                    437 non-null    object 
 16  Contributing Factors. NONE                                                                                                                                     437 non-null    object 
 17  Main Factor. Which of these was the main factor for leaving?                                                                                                   113 non-null    object 
 18  InstituteViews. Topic:1. I feel the senior leadership had a clear vision and direction                                                                         608 non-null    object 
 19  InstituteViews. Topic:2. I was given access to skills training to help me do my job better                                                                     613 non-null    object 
 20  InstituteViews. Topic:3. I was given adequate opportunities for personal development                                                                           610 non-null    object 
 21  InstituteViews. Topic:4. I was given adequate opportunities for promotion within %Institute]Q25LBL%                                                            608 non-null    object 
 22  InstituteViews. Topic:5. I felt the salary for the job was right for the responsibilities I had                                                                615 non-null    object 
 23  InstituteViews. Topic:6. The organisation recognised when staff did good work                                                                                  607 non-null    object 
 24  InstituteViews. Topic:7. Management was generally supportive of me                                                                                             614 non-null    object 
 25  InstituteViews. Topic:8. Management was generally supportive of my team                                                                                        608 non-null    object 
 26  InstituteViews. Topic:9. I was kept informed of the changes in the organisation which would affect me                                                          610 non-null    object 
 27  InstituteViews. Topic:10. Staff morale was positive within the Institute                                                                                       602 non-null    object 
 28  InstituteViews. Topic:11. If I had a workplace issue it was dealt with quickly                                                                                 601 non-null    object 
 29  InstituteViews. Topic:12. If I had a workplace issue it was dealt with efficiently                                                                             597 non-null    object 
 30  InstituteViews. Topic:13. If I had a workplace issue it was dealt with discreetly                                                                              601 non-null    object 
 31  WorkUnitViews. Topic:14. I was satisfied with the quality of the management and supervision within my work unit                                                609 non-null    object 
 32  WorkUnitViews. Topic:15. I worked well with my colleagues                                                                                                      605 non-null    object 
 33  WorkUnitViews. Topic:16. My job was challenging and interesting                                                                                                607 non-null    object 
 34  WorkUnitViews. Topic:17. I was encouraged to use my initiative in the course of my work                                                                        610 non-null    object 
 35  WorkUnitViews. Topic:18. I had sufficient contact with other people in my job                                                                                  613 non-null    object 
 36  WorkUnitViews. Topic:19. I was given adequate support and co-operation by my peers to enable me to do my job                                                   609 non-null    object 
 37  WorkUnitViews. Topic:20. I was able to use the full range of my skills in my job                                                                               609 non-null    object 
 38  WorkUnitViews. Topic:21. I was able to use the full range of my abilities in my job. ; Category:Level of Agreement; Question:YOUR VIEWS ABOUT YOUR WORK UNIT]  608 non-null    object 
 39  WorkUnitViews. Topic:22. I was able to use the full range of my knowledge in my job                                                                            608 non-null    object 
 40  WorkUnitViews. Topic:23. My job provided sufficient variety                                                                                                    611 non-null    object 
 41  WorkUnitViews. Topic:24. I was able to cope with the level of stress and pressure in my job                                                                    610 non-null    object 
 42  WorkUnitViews. Topic:25. My job allowed me to balance the demands of work and family to my satisfaction                                                        611 non-null    object 
 43  WorkUnitViews. Topic:26. My supervisor gave me adequate personal recognition and feedback on my performance                                                    606 non-null    object 
 44  WorkUnitViews. Topic:27. My working environment was satisfactory e.g. sufficient space, good lighting, suitable seating and working area                       610 non-null    object 
 45  WorkUnitViews. Topic:28. I was given the opportunity to mentor and coach others in order for me to pass on my skills and knowledge prior to my cessation date  609 non-null    object 
 46  WorkUnitViews. Topic:29. There was adequate communication between staff in my unit                                                                             603 non-null    object 
 47  WorkUnitViews. Topic:30. Staff morale was positive within my work unit                                                                                         606 non-null    object 
 48  Induction. Did you undertake Workplace Induction?                                                                                                              619 non-null    object 
 49  InductionInfo. Topic:Did you undertake a Corporate Induction?                                                                                                  432 non-null    object 
 50  InductionInfo. Topic:Did you undertake a Institute Induction?                                                                                                  483 non-null    object 
 51  InductionInfo. Topic: Did you undertake Team Induction?                                                                                                        440 non-null    object 
 52  InductionInfo. Face to Face Topic:Did you undertake a Corporate Induction; Category:How it was conducted?                                                      555 non-null    object 
 53  InductionInfo. On-line Topic:Did you undertake a Corporate Induction; Category:How it was conducted?                                                           555 non-null    object 
 54  InductionInfo. Induction Manual Topic:Did you undertake a Corporate Induction?                                                                                 555 non-null    object 
 55  InductionInfo. Face to Face Topic:Did you undertake a Institute Induction?                                                                                     530 non-null    object 
 56  InductionInfo. On-line Topic:Did you undertake a Institute Induction?                                                                                          555 non-null    object 
 57  InductionInfo. Induction Manual Topic:Did you undertake a Institute Induction?                                                                                 553 non-null    object 
 58  InductionInfo. Face to Face Topic: Did you undertake Team Induction; Category?                                                                                 555 non-null    object 
 59  InductionInfo. On-line Topic: Did you undertake Team Induction?process you undertook and how it was conducted.]                                                555 non-null    object 
 60  InductionInfo. Induction Manual Topic: Did you undertake Team Induction?                                                                                       555 non-null    object 
 61  Workplace. Topic:Did you and your Manager develop a Performance and Professional Development Plan (PPDP)?                                                      608 non-null    object 
 62  Workplace. Topic:Does your workplace promote a work culture free from all forms of unlawful discrimination?                                                    594 non-null    object 
 63  Workplace. Topic:Does your workplace promote and practice the principles of employment equity?                                                                 587 non-null    object 
 64  Workplace. Topic:Does your workplace value the diversity of its employees?                                                                                     586 non-null    object 
 65  Workplace. Topic:Would you recommend the Institute as an employer to others?                                                                                   581 non-null    object 
 66  Gender. What is your Gender?                                                                                                                                   596 non-null    object 
 67  CurrentAge. Current Age                                                                                                                                        596 non-null    object 
 68  Employment Type. Employment Type                                                                                                                               596 non-null    object 
 69  Classification. Classification                                                                                                                                 596 non-null    object 
 70  LengthofServiceOverall. Overall Length of Service at Institute (in years)                                                                                      596 non-null    object 
 71  LengthofServiceCurrent. Length of Service at current workplace (in years)                                                                                      596 non-null    object 
dtypes: float64(2), object(70)
memory usage: 395.0+ KB
Out[7]:
Record ID Institute WorkArea CESSATION YEAR Reason for ceasing employment Contributing Factors. Career Move - Public Sector Contributing Factors. Career Move - Private Sector Contributing Factors. Career Move - Self-employment Contributing Factors. Ill Health Contributing Factors. Maternity/Family ... Workplace. Topic:Does your workplace promote a work culture free from all forms of unlawful discrimination? Workplace. Topic:Does your workplace promote and practice the principles of employment equity? Workplace. Topic:Does your workplace value the diversity of its employees? Workplace. Topic:Would you recommend the Institute as an employer to others? Gender. What is your Gender? CurrentAge. Current Age Employment Type. Employment Type Classification. Classification LengthofServiceOverall. Overall Length of Service at Institute (in years) LengthofServiceCurrent. Length of Service at current workplace (in years)
0 6.341330e+17 Southern Queensland Institute of TAFE Non-Delivery (corporate) 2010.0 Contract Expired NaN NaN NaN NaN NaN ... Yes Yes Yes Yes Female 26 30 Temporary Full-time Administration (AO) 1-2 1-2
1 6.341337e+17 Mount Isa Institute of TAFE Non-Delivery (corporate) 2010.0 Retirement - - - - - ... Yes Yes Yes Yes NaN NaN NaN NaN NaN NaN
2 6.341388e+17 Mount Isa Institute of TAFE Delivery (teaching) 2010.0 Retirement - - - - - ... Yes Yes Yes Yes NaN NaN NaN NaN NaN NaN
3 6.341399e+17 Mount Isa Institute of TAFE Non-Delivery (corporate) 2010.0 Resignation - - - - - ... Yes Yes Yes Yes NaN NaN NaN NaN NaN NaN
4 6.341466e+17 Southern Queensland Institute of TAFE Delivery (teaching) 2010.0 Resignation - Career Move - Private Sector - - - ... Yes Yes Yes Yes Male 41 45 Permanent Full-time Teacher (including LVT) 3-4 3-4

5 rows × 72 columns

In [8]:
# Exploring No. of missing values in each column of TAFE survey Dataset
print('No. of missing values in each column of TAFE Survey Data set')
print('------------------------------------------------------------')
tafe_survey.isnull().sum()
No. of missing values in each column of TAFE Survey Data set
------------------------------------------------------------
Out[8]:
Record ID                                                                      0
Institute                                                                      0
WorkArea                                                                       0
CESSATION YEAR                                                                 7
Reason for ceasing employment                                                  1
                                                                            ... 
CurrentAge. Current Age                                                      106
Employment Type. Employment Type                                             106
Classification. Classification                                               106
LengthofServiceOverall. Overall Length of Service at Institute (in years)    106
LengthofServiceCurrent. Length of Service at current workplace (in years)    106
Length: 72, dtype: int64
In [9]:
# Grouping null value percentages in TAFE survey dataset 
(tafe_survey.isnull().sum()/702).value_counts(normalize = True, bins = 10).sort_index(ascending = True)
Out[9]:
(-0.001839, 0.0839]    0.069444
(0.0839, 0.168]        0.569444
(0.168, 0.252]         0.138889
(0.252, 0.336]         0.013889
(0.336, 0.42]          0.194444
(0.42, 0.503]          0.000000
(0.503, 0.587]         0.000000
(0.587, 0.671]         0.000000
(0.671, 0.755]         0.000000
(0.755, 0.839]         0.013889
dtype: float64

Observations

DETE Survey Dataset

  • DETE Survey Dataset consist of 822 entries under the 56 columns.
  • Around 86% of the columns contain less that 10% of null values while around 9% of columns comprise more than 90% null values.
  • The values Not Stated has used in some cells to indicate missing values but those values are not considered as null values.

TAFE Survey Dataset

  • TAFE Survey Dataset consist of 702 entries under the 72 columns.
  • Around 64% of the columns contain les than 17% of null values while about 1% of contain more than 75% of null values.
  • The values - has used in some cells to indicate missing values but those values are not considered as null values.

Both the dataset contain many columns that we don't need to complete our analysis.

Each dataset contains many of the same columns, but the column names are different.

Rereading the datasets

Purpose of this reading is to fix the missing values. In here we use pd.read_csv() function to specify values that should be represented as NaN.

In [10]:
# Reread the DETE dataset to specify values that should be represented as NaN
dete_survey = pd.read_csv('dete_survey.csv', na_values='Not Stated')
In [11]:
# Exploring DETE dataset
dete_survey.head()
Out[11]:
ID SeparationType Cease Date DETE Start Date Role Start Date Position Classification Region Business Unit Employment Status ... Kept informed Wellness programs Health & Safety Gender Age Aboriginal Torres Strait South Sea Disability NESB
0 1 Ill Health Retirement 08/2012 1984.0 2004.0 Public Servant A01-A04 Central Office Corporate Strategy and Peformance Permanent Full-time ... N N N Male 56-60 NaN NaN NaN NaN Yes
1 2 Voluntary Early Retirement (VER) 08/2012 NaN NaN Public Servant AO5-AO7 Central Office Corporate Strategy and Peformance Permanent Full-time ... N N N Male 56-60 NaN NaN NaN NaN NaN
2 3 Voluntary Early Retirement (VER) 05/2012 2011.0 2011.0 Schools Officer NaN Central Office Education Queensland Permanent Full-time ... N N N Male 61 or older NaN NaN NaN NaN NaN
3 4 Resignation-Other reasons 05/2012 2005.0 2006.0 Teacher Primary Central Queensland NaN Permanent Full-time ... A N A Female 36-40 NaN NaN NaN NaN NaN
4 5 Age Retirement 05/2012 1970.0 1989.0 Head of Curriculum/Head of Special Education NaN South East NaN Permanent Full-time ... N A M Female 61 or older NaN NaN NaN NaN NaN

5 rows × 56 columns

Dropping some columns

In [12]:
# Drop column No. 28 to 49 of DETE dataset and assign the result to dete_survey_updated
dete_survey_updated = dete_survey.drop(dete_survey.columns[28:49], axis=1)
In [13]:
# Drop column No. 17 to 66 of TAFE dataset and assign the result to tafe_survey_updated
tafe_survey_updated = tafe_survey.drop(tafe_survey.columns[17:66], axis = 1)
In [14]:
print("------------------------------------------------------ ")
print("|       Description           |  Before   | After     |")
print("------------------------------------------------------ ")
print("|Shape of DETE survey dataset |", dete_survey.shape, "|" ,dete_survey_updated.shape, "|")
print("------------------------------------------------------ ")
print("|Shape of TAFE survey dataset |", tafe_survey.shape, "|" ,tafe_survey_updated.shape, "|")
print("------------------------------------------------------ ")
------------------------------------------------------ 
|       Description           |  Before   | After     |
------------------------------------------------------ 
|Shape of DETE survey dataset | (822, 56) | (822, 35) |
------------------------------------------------------ 
|Shape of TAFE survey dataset | (702, 72) | (702, 23) |
------------------------------------------------------ 

We dropped some columns from each dataframe that we don't use in our analysis to make the dataframes easier to work with. By doing this we were able to reduce the sizes of both dataframes significantly.

Renaming the columns

In [15]:
# Modify all columns of DETE survey dataset
dete_survey_updated.columns = dete_survey_updated.columns.str.lower().str.strip().str.replace(' ', '_')
dete_survey_updated.columns
Out[15]:
Index(['id', 'separationtype', 'cease_date', 'dete_start_date',
       'role_start_date', 'position', 'classification', 'region',
       'business_unit', 'employment_status', 'career_move_to_public_sector',
       'career_move_to_private_sector', 'interpersonal_conflicts',
       'job_dissatisfaction', 'dissatisfaction_with_the_department',
       'physical_work_environment', 'lack_of_recognition',
       'lack_of_job_security', 'work_location', 'employment_conditions',
       'maternity/family', 'relocation', 'study/travel', 'ill_health',
       'traumatic_incident', 'work_life_balance', 'workload',
       'none_of_the_above', 'gender', 'age', 'aboriginal', 'torres_strait',
       'south_sea', 'disability', 'nesb'],
      dtype='object')
In [16]:
# Rename some columns of TAFE survey dataset
new_col_nm = {'Record ID': 'id', 'CESSATION YEAR': 'cease_date', 'Reason for ceasing employment': 'separationtype', 
              'Gender. What is your Gender?': 'gender', 'CurrentAge. Current Age': 'age', 
              'Employment Type. Employment Type': 'employment_status', 'Classification. Classification': 'position',
              'LengthofServiceOverall. Overall Length of Service at Institute (in years)': 'institute_service',
              'LengthofServiceCurrent. Length of Service at current workplace (in years)': 'role_service'}

tafe_survey_updated.rename(new_col_nm, axis=1, inplace=True)
tafe_survey_updated.columns
Out[16]:
Index(['id', 'Institute', 'WorkArea', 'cease_date', 'separationtype',
       'Contributing Factors. Career Move - Public Sector ',
       'Contributing Factors. Career Move - Private Sector ',
       'Contributing Factors. Career Move - Self-employment',
       'Contributing Factors. Ill Health',
       'Contributing Factors. Maternity/Family',
       'Contributing Factors. Dissatisfaction',
       'Contributing Factors. Job Dissatisfaction',
       'Contributing Factors. Interpersonal Conflict',
       'Contributing Factors. Study', 'Contributing Factors. Travel',
       'Contributing Factors. Other', 'Contributing Factors. NONE', 'gender',
       'age', 'employment_status', 'position', 'institute_service',
       'role_service'],
      dtype='object')

Each dataframe contains many of the same columns, but the column names are different. Some of the columns those will be used for our final analysis were renamed as eventually those columns want to be combined, the column names had to be standardized.

Filtering out resignation datasets

In [17]:
# Explore the separationtype column of DETE survey dataset to identify the resignation data
dete_survey_updated["separationtype"].value_counts()
Out[17]:
Age Retirement                          285
Resignation-Other reasons               150
Resignation-Other employer               91
Resignation-Move overseas/interstate     70
Voluntary Early Retirement (VER)         67
Ill Health Retirement                    61
Other                                    49
Contract Expired                         34
Termination                              15
Name: separationtype, dtype: int64
In [18]:
# Explore the separationtype column of TAFE survey dataset to identify the resignation data
tafe_survey_updated["separationtype"].value_counts()
Out[18]:
Resignation                 340
Contract Expired            127
Retrenchment/ Redundancy    104
Retirement                   82
Transfer                     25
Termination                  23
Name: separationtype, dtype: int64
In [19]:
# Filter out resignation data from DETE survey dataset and assign to dete_resignations dataframe
dete_resignations = dete_survey_updated[dete_survey_updated["separationtype"].str.contains("Resignation")].copy()
dete_resignations["separationtype"].value_counts()
Out[19]:
Resignation-Other reasons               150
Resignation-Other employer               91
Resignation-Move overseas/interstate     70
Name: separationtype, dtype: int64
In [20]:
# Filter out resignation data from TAFE survey dataset and assign to tafe_resignations dataframe
tafe_resignations = tafe_survey_updated[tafe_survey_updated["separationtype"]=="Resignation"].copy()
tafe_resignations["separationtype"].value_counts()
Out[20]:
Resignation    340
Name: separationtype, dtype: int64

Our objective is to analyze the reasons for resignation and therefore we'll only analyze survey respondents who resigned. However separationtype columns in each dataframe contains a couple of different separation types. So other separationtypes of the datasets were filtered out.

Verifying the data

In [21]:
# Explore DETE resignation dataset to figure out the treatment required
dete_resignations["cease_date"].value_counts()
Out[21]:
2012       126
2013        74
01/2014     22
12/2013     17
06/2013     14
09/2013     11
07/2013      9
11/2013      9
10/2013      6
08/2013      4
05/2013      2
05/2012      2
07/2012      1
07/2006      1
09/2010      1
2010         1
Name: cease_date, dtype: int64
In [22]:
# Extract the ceased years from values in cease_date column and replace values in cease_date column with respective ceased years
p1 = r"([1-2][0-9]{3})"
dete_resignations["cease_date"] = dete_resignations["cease_date"].str.extract(p1).astype('float')
dete_resignations["cease_date"].value_counts()
Out[22]:
2013.0    146
2012.0    129
2014.0     22
2010.0      2
2006.0      1
Name: cease_date, dtype: int64
In [23]:
# Arrange values in dete_start_date column in ascending order to identify any unrealistic values in DETE resignation dataset 
dete_resignations["dete_start_date"].value_counts().sort_index(ascending = True)
Out[23]:
1963.0     1
1971.0     1
1972.0     1
1973.0     1
1974.0     2
1975.0     1
1976.0     2
1977.0     1
1980.0     5
1982.0     1
1983.0     2
1984.0     1
1985.0     3
1986.0     3
1987.0     1
1988.0     4
1989.0     4
1990.0     5
1991.0     4
1992.0     6
1993.0     5
1994.0     6
1995.0     4
1996.0     6
1997.0     5
1998.0     6
1999.0     8
2000.0     9
2001.0     3
2002.0     6
2003.0     6
2004.0    14
2005.0    15
2006.0    13
2007.0    21
2008.0    22
2009.0    13
2010.0    17
2011.0    24
2012.0    21
2013.0    10
Name: dete_start_date, dtype: int64
In [24]:
# Generate boxplot to identify any unrealistic values in DETE resignation dataset
dete_resignations.boxplot(column=["cease_date", "dete_start_date"])
Out[24]:
<matplotlib.axes._subplots.AxesSubplot at 0x1d09a038550>
In [25]:
# Arrange values in cease_date column in ascending order to identify any unrealistic values in TAFE resignation dataset
tafe_resignations["cease_date"].value_counts().sort_index(ascending=True)
Out[25]:
2009.0      2
2010.0     68
2011.0    116
2012.0     94
2013.0     55
Name: cease_date, dtype: int64
In [26]:
# Generate boxplot to identify any unrealistic values in TAFE resignation dataset
tafe_resignations.boxplot(column="cease_date")
Out[26]:
<matplotlib.axes._subplots.AxesSubplot at 0x1d09a79bb20>

According to the outputs above, it can be seen that there are no any unrealistic values in cease_date and dete_start_date columns of dete_resignations dataset and in cease_date column of cafe_resignations dataset.

In [27]:
# Calculate the No. of years each employees served for DETE and assign the values to institute_service column
dete_resignations["institute_service"] = dete_resignations["cease_date"] - dete_resignations["dete_start_date"]
dete_resignations.head()
Out[27]:
id separationtype cease_date dete_start_date role_start_date position classification region business_unit employment_status ... workload none_of_the_above gender age aboriginal torres_strait south_sea disability nesb institute_service
3 4 Resignation-Other reasons 2012.0 2005.0 2006.0 Teacher Primary Central Queensland NaN Permanent Full-time ... False False Female 36-40 NaN NaN NaN NaN NaN 7.0
5 6 Resignation-Other reasons 2012.0 1994.0 1997.0 Guidance Officer NaN Central Office Education Queensland Permanent Full-time ... False False Female 41-45 NaN NaN NaN NaN NaN 18.0
8 9 Resignation-Other reasons 2012.0 2009.0 2009.0 Teacher Secondary North Queensland NaN Permanent Full-time ... False False Female 31-35 NaN NaN NaN NaN NaN 3.0
9 10 Resignation-Other employer 2012.0 1997.0 2008.0 Teacher Aide NaN NaN NaN Permanent Part-time ... False False Female 46-50 NaN NaN NaN NaN NaN 15.0
11 12 Resignation-Move overseas/interstate 2012.0 2009.0 2009.0 Teacher Secondary Far North Queensland NaN Permanent Full-time ... False False Male 31-35 NaN NaN NaN NaN NaN 3.0

5 rows × 36 columns

The length of time an employee spent in a workplace is essential for our analyze. The tafe_resignations dataframe already contains these data in the column named institute_service while the dete_resignations dataframe doesn't contain such a data. However, it contain service started year and service ended year in dete_start_dateand cease_datecolumns respectively. So these 02 columns were used to calculate the service and added it into the dataframe under the new column named institute_service.

In [28]:
# Explore "Contributing Factors. Dissatisfaction" column to identify different types of values in that column
tafe_resignations["Contributing Factors. Dissatisfaction"].value_counts()
Out[28]:
-                                         277
Contributing Factors. Dissatisfaction      55
Name: Contributing Factors. Dissatisfaction, dtype: int64
In [29]:
# Explore "Contributing Factors. Job Dissatisfaction" column to identify different types of values in that column
tafe_resignations["Contributing Factors. Job Dissatisfaction"].value_counts()
Out[29]:
-                      270
Job Dissatisfaction     62
Name: Contributing Factors. Job Dissatisfaction, dtype: int64
In [30]:
# Define a function to update the values in specific columns with "True", "False" and "NaN" values
def update_vals(element):
    if pd.isnull(element):
        return np.nan
    elif element == '-':
        return False
    else:
        return True
    
# Group the columns needed to apply the function above 
tafe_factors = ["Contributing Factors. Dissatisfaction", "Contributing Factors. Job Dissatisfaction"]

# Apply the function defined above with "df.any()" method and add result to "dissatisfied" column
tafe_resignations["dissatisfied"] = tafe_resignations[tafe_factors].applymap(update_vals).any(1, skipna = False)

# Create a copy of "tafe_resignations" dataset and assign it to "tafe_resignations_up"
tafe_resignations_up = tafe_resignations.copy()

# Explore different value types and counts of each value types in "dissatisfied" column
tafe_resignations_up["dissatisfied"].value_counts(dropna=False)
Out[30]:
False    241
True      91
NaN        8
Name: dissatisfied, dtype: int64
In [31]:
# Group the columns needed to apply the "df.any()" method
dete_factors = ["job_dissatisfaction", "dissatisfaction_with_the_department", "physical_work_environment",
               "lack_of_recognition", "lack_of_job_security", "work_location", "employment_conditions",
               "work_life_balance", "workload"]

# Apply the "df.any()" method and add result to "dissatisfied" column
dete_resignations["dissatisfied"] = dete_resignations[dete_factors].any(1, skipna = False)

# Create a copy of "dete_resignations" dataset and assign it to "dete_resignations_up"
dete_resignations_up = dete_resignations.copy()

# Explore different value types and counts of each value types in "dissatisfied" column
dete_resignations_up["dissatisfied"].value_counts(dropna=False)
Out[31]:
False    162
True     149
Name: dissatisfied, dtype: int64

In order to achieve the objective, we need to identify any employees who resigned because they were dissatisfied. In each dataframe there are several columns that can be used to categorize employees as dissatisfied. If the employee indicated any of the factors in those columns caused them to resign, we maked them as dissatisfied in a new column.

In this exercise, we had to convert Contributing Factors. Dissatisfaction and Contributing Factors. Job Dissatisfaction columns in the tafe_resignations dataframe to True, False, or NaN values.

Combining the data

In [32]:
# Add a column to each dataframe that will allow us to easily distinguish between the two datasets
dete_resignations_up["institute"] = "DETE"
tafe_resignations_up["institute"] = "TAFE"
In [33]:
# Combine the dataframes and assign the result to "combined"
combined = pd.concat([dete_resignations_up, tafe_resignations_up], ignore_index=True)
In [34]:
# Explore the "combined" dataframe
combined.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 651 entries, 0 to 650
Data columns (total 53 columns):
 #   Column                                               Non-Null Count  Dtype  
---  ------                                               --------------  -----  
 0   id                                                   651 non-null    float64
 1   separationtype                                       651 non-null    object 
 2   cease_date                                           635 non-null    float64
 3   dete_start_date                                      283 non-null    float64
 4   role_start_date                                      271 non-null    float64
 5   position                                             598 non-null    object 
 6   classification                                       161 non-null    object 
 7   region                                               265 non-null    object 
 8   business_unit                                        32 non-null     object 
 9   employment_status                                    597 non-null    object 
 10  career_move_to_public_sector                         311 non-null    object 
 11  career_move_to_private_sector                        311 non-null    object 
 12  interpersonal_conflicts                              311 non-null    object 
 13  job_dissatisfaction                                  311 non-null    object 
 14  dissatisfaction_with_the_department                  311 non-null    object 
 15  physical_work_environment                            311 non-null    object 
 16  lack_of_recognition                                  311 non-null    object 
 17  lack_of_job_security                                 311 non-null    object 
 18  work_location                                        311 non-null    object 
 19  employment_conditions                                311 non-null    object 
 20  maternity/family                                     311 non-null    object 
 21  relocation                                           311 non-null    object 
 22  study/travel                                         311 non-null    object 
 23  ill_health                                           311 non-null    object 
 24  traumatic_incident                                   311 non-null    object 
 25  work_life_balance                                    311 non-null    object 
 26  workload                                             311 non-null    object 
 27  none_of_the_above                                    311 non-null    object 
 28  gender                                               592 non-null    object 
 29  age                                                  596 non-null    object 
 30  aboriginal                                           7 non-null      object 
 31  torres_strait                                        0 non-null      object 
 32  south_sea                                            3 non-null      object 
 33  disability                                           8 non-null      object 
 34  nesb                                                 9 non-null      object 
 35  institute_service                                    563 non-null    object 
 36  dissatisfied                                         643 non-null    object 
 37  institute                                            651 non-null    object 
 38  Institute                                            340 non-null    object 
 39  WorkArea                                             340 non-null    object 
 40  Contributing Factors. Career Move - Public Sector    332 non-null    object 
 41  Contributing Factors. Career Move - Private Sector   332 non-null    object 
 42  Contributing Factors. Career Move - Self-employment  332 non-null    object 
 43  Contributing Factors. Ill Health                     332 non-null    object 
 44  Contributing Factors. Maternity/Family               332 non-null    object 
 45  Contributing Factors. Dissatisfaction                332 non-null    object 
 46  Contributing Factors. Job Dissatisfaction            332 non-null    object 
 47  Contributing Factors. Interpersonal Conflict         332 non-null    object 
 48  Contributing Factors. Study                          332 non-null    object 
 49  Contributing Factors. Travel                         332 non-null    object 
 50  Contributing Factors. Other                          332 non-null    object 
 51  Contributing Factors. NONE                           332 non-null    object 
 52  role_service                                         290 non-null    object 
dtypes: float64(4), object(49)
memory usage: 269.7+ KB
In [35]:
# Drop any columns with less than 500 non null values
combined_updated = combined.dropna(axis=1,thresh=500)
In [36]:
# Explore the "combined_updated" dataframe
combined_updated.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 651 entries, 0 to 650
Data columns (total 10 columns):
 #   Column             Non-Null Count  Dtype  
---  ------             --------------  -----  
 0   id                 651 non-null    float64
 1   separationtype     651 non-null    object 
 2   cease_date         635 non-null    float64
 3   position           598 non-null    object 
 4   employment_status  597 non-null    object 
 5   gender             592 non-null    object 
 6   age                596 non-null    object 
 7   institute_service  563 non-null    object 
 8   dissatisfied       643 non-null    object 
 9   institute          651 non-null    object 
dtypes: float64(2), object(8)
memory usage: 51.0+ KB

First, a column was added to each dataframe that allow us to easily distinguish between the two and then two dataframes were combined.

However,after combining the dataframes we still have some columns left in the combined dataframe that we don't need to complete our analysis. Therefore we dropped all those column if each column didn't have 500 values or more. This exercise caused to reduce the size of combined dataframe notably as it dropped 43 columns.

Cleaning the service column

In [37]:
# Explore different value types and counts of each value types in "institute_service" column
combined_updated["institute_service"].value_counts(dropna=False)
Out[37]:
NaN                   88
Less than 1 year      73
1-2                   64
3-4                   63
5-6                   33
11-20                 26
5.0                   23
1.0                   22
7-10                  21
0.0                   20
3.0                   20
6.0                   17
4.0                   16
9.0                   14
2.0                   14
7.0                   13
More than 20 years    10
8.0                    8
13.0                   8
15.0                   7
20.0                   7
10.0                   6
12.0                   6
14.0                   6
17.0                   6
22.0                   6
18.0                   5
16.0                   5
11.0                   4
23.0                   4
24.0                   4
19.0                   3
32.0                   3
39.0                   3
21.0                   3
28.0                   2
30.0                   2
26.0                   2
36.0                   2
25.0                   2
29.0                   1
31.0                   1
27.0                   1
34.0                   1
35.0                   1
38.0                   1
41.0                   1
42.0                   1
49.0                   1
33.0                   1
Name: institute_service, dtype: int64
In [38]:
# Create a copy of "combined_updated" dataset and assign it to "combined_updated_up"
combined_updated_up = combined_updated.copy()

# Extract the years of service from each value in the "institute_service" column
combined_updated_up["institute_service"] = combined_updated_up["institute_service"].astype('str')
combined_updated_up["institute_service"] = combined_updated_up["institute_service"].str.extract(r'(\d+)')
combined_updated_up["institute_service"] = combined_updated_up["institute_service"].astype('float')
In [39]:
# Explore different value types and counts of each value types in "institute_service" column
combined_updated_up["institute_service"].value_counts(dropna=False)
Out[39]:
1.0     159
NaN      88
3.0      83
5.0      56
7.0      34
11.0     30
0.0      20
20.0     17
6.0      17
4.0      16
9.0      14
2.0      14
13.0      8
8.0       8
15.0      7
22.0      6
10.0      6
17.0      6
14.0      6
12.0      6
16.0      5
18.0      5
24.0      4
23.0      4
21.0      3
39.0      3
32.0      3
19.0      3
36.0      2
30.0      2
25.0      2
26.0      2
28.0      2
42.0      1
29.0      1
35.0      1
27.0      1
41.0      1
49.0      1
38.0      1
34.0      1
33.0      1
31.0      1
Name: institute_service, dtype: int64
In [40]:
# Define a function, that categorizes employees according to the amount of years spent in their workplace
def service_cats(val):
    if pd.isnull(val):
        return np.nan
    elif val < 3:
        return "New"
    elif val <= 6:
        return "Experienced"
    elif val <= 10:
        return "Established"
    else:
        return "Veteran"

# Apply the function defined above to "institute_service" column and assign the results to "service_cat" column
combined_updated_up["service_cat"] = combined_updated_up["institute_service"].apply(service_cats)

# Explore different value types and counts of each value types in "service_cat" column
combined_updated_up["service_cat"].value_counts(dropna = False)
Out[40]:
New            193
Experienced    172
Veteran        136
NaN             88
Established     62
Name: service_cat, dtype: int64

First we analyzed the institute_service column that contained values in a couple different forms as we need to apply right cleaning technique according to the form of data. Then we cleaned the column and categorized all service years in to four categories based on definitions below:

  • New: Less than 3 years at a company
  • Experienced: 3-6 years at a company
  • Established: 7-10 years at a company
  • Veteran: 11 or more years at a company

Finally, we created a service_cat column, that categorizes employees according to the amount of years spent in their workplace.

Performing initial analysis

In [41]:
# Explore different value types and counts of each value types in "dissatisfied" column
combined_updated_up["dissatisfied"].value_counts(dropna=False)
Out[41]:
False    403
True     240
NaN        8
Name: dissatisfied, dtype: int64
In [42]:
# Replace the missing values in the "dissatisfied" column with the value that occurs most frequently in this column
combined_updated_up["dissatisfied"].fillna(value=False, inplace=True)

# Explore different value types and counts of each value types in "dissatisfied" column
combined_updated_up["dissatisfied"].value_counts(dropna=False)
Out[42]:
False    411
True     240
Name: dissatisfied, dtype: int64
In [43]:
# Calculate the percentage of dissatisfied employees in each service_cat group
pvt_dst_ser = combined_updated_up.pivot_table(values="dissatisfied", index="service_cat")

# Plot the results with barplot
pvt_dst_ser.plot(kind="bar")

# Define Y-axis lable
plt.ylabel("Percentage of dissatisfied Employees")

# Define title of the graph
plt.title("Dissatisfied percentage - service category wise")

# Hide legend
plt.legend().set_visible(False)
In [44]:
# Explore different value types and counts of each value types in "age" column
combined_updated_up["age"].value_counts(dropna = False)
Out[44]:
51-55            71
NaN              55
41-45            48
41  45           45
46-50            42
36-40            41
46  50           39
26-30            35
21  25           33
31  35           32
36  40           32
26  30           32
56 or older      29
21-25            29
31-35            29
56-60            26
61 or older      23
20 or younger    10
Name: age, dtype: int64
In [45]:
# Replace white space with dash and assign updated values to "age_group" column
combined_updated_up["age_group"] = combined_updated_up["age"].str.replace("  ", "-")
In [46]:
# Define a function to update some values
def age_groups(val):
    if val == "56-60" or val == "61 or older":
        return "56 or older"
    elif pd.isnull(val):
        return np.nan
    else:
        return val

# Apply function and dceate new column with updated values
combined_updated_up["age_group"] = combined_updated_up["age_group"].apply(age_groups)

# Explore different value types and counts of each value types in "age_group" column
combined_updated_up["age_group"].value_counts(dropna = False)
Out[46]:
41-45            93
46-50            81
56 or older      78
36-40            73
51-55            71
26-30            67
21-25            62
31-35            61
NaN              55
20 or younger    10
Name: age_group, dtype: int64
In [47]:
# Replace the missing values in the "age_group" column by propagating non-null values forward
combined_updated_up["age_group"].fillna(method='ffill', inplace=True)

# Explore different value types and counts of each value types in "age_group" column
combined_updated_up["age_group"].value_counts(dropna = False)
Out[47]:
41-45            101
46-50             92
56 or older       84
51-55             77
36-40             76
26-30             73
31-35             71
21-25             66
20 or younger     11
Name: age_group, dtype: int64
In [48]:
# Define a function, that categorizes employees according to their ages
def age_cats(val):
    if val == "20 or younger" or val == "21-25" or val == "26-30":
        return "Younger Age"
    elif val == "31-35" or val == "36-40" or val == "41-45":
        return "Middle Age"
    else:
        return "Older Age"

# Apply function and dceate new column with updated values
combined_updated_up["age_category"] = combined_updated_up["age_group"].apply(age_cats)
combined_updated_up["age_category"].value_counts(dropna = False)
Out[48]:
Older Age      253
Middle Age     248
Younger Age    150
Name: age_category, dtype: int64
In [49]:
# Calculate the percentage of dissatisfied employees in each age_category group
pvt_dst_age = combined_updated_up.pivot_table(values="dissatisfied", index="age_category")

# Plot the results with barplot
pvt_dst_age.plot(kind="bar")

# Define Y-axis lable
plt.ylabel("Percentage of dissatisfied Employees")

# Define title of the graph
plt.title("Dissatisfied percentage - Age group wise")

# Hide legend
plt.legend().set_visible(False)

Conclution

We have achieved our goal after so many data cleaning, combining, aggregation and analysis. Based on our analysis, we can conclude the output of the analysis as follows:

  • According to the first graph, we can see that the established employees are more likely to resign due to dissatisfaction while the new employees are the least resigned due to dissatisfaction.
  • The second graph reveals that older employees are more prone to resign due to dissatisfaction, while younger employees are generally less likely to resign due to dissatisfaction.