This is part two of my Blended Retirement System study. For more information please see my original article, BRS Part 1.
Now that I've made a sample program which can take a user input and provide the correct pay rate, I'm going to do two things: convert my code to Python 3 (3.6) and begin creating a simulation program.
The original program was written in Python 2.7 which is a legacy standard still widely used. However since I have the luxury of forward planning I will go ahead and switch to Python 3 before it becomes burdensome.
I did experiment with some code conversion methods that promise to mostly automate the process, but it quickly became apparent I was just bloating my simple program. Therefore I just made the necessary changes, input and print updates.
CalcPay in Python 3
#!/usr/bin/env python3
"""Calculate 2017 US military service base pay. By Wolf"""
import sys
import math
import pandas as pd
def find_pay(rank, years, active, enlisted):
"""Returns cell value given rank, years, duty status, enlisted status"""
if active == 1:
if enlisted == 1:
ae_read = pd.read_csv('E_2017pay.csv')
ae1 = pd.DataFrame(ae_read)
a_e = ae1.set_index('Years')
pay = a_e.loc[years, rank]
return pay
elif enlisted == 0:
ao_read = pd.read_csv('O_2017pay.csv')
ao1 = pd.DataFrame(ao_read)
a_o = ao1.set_index('Years')
pay = a_o.loc[years, rank]
return pay
elif active == 0:
if enlisted == 1:
re_read = pd.read_csv('RE_2017pay.csv')
re1 = pd.DataFrame(re_read)
r_e = re1.set_index('Years')
pay = r_e.loc[years, rank]
return pay
elif enlisted == 0:
ro_read = pd.read_csv('RO_2017pay.csv')
ro1 = pd.DataFrame(ro_read)
r_o = ro1.set_index('Years')
pay = r_o.loc[years, rank]
return pay
def determine_years(service):
"""Takes years of service, choose experience range assigns to years"""
if 0 <= service < 2:
years = 'Under 2'
elif 2 <= service < 3:
years = 'Over 2'
elif 3 <= service < 4:
years = 'Over 3'
elif 4 <= service < 6:
years = 'Over 4'
elif 6 <= service < 8:
years = 'Over 6'
elif 8 <= service < 10:
years = 'Over 8'
elif 10 <= service < 12:
years = 'Over 10'
elif 12 <= service < 14:
years = 'Over 12'
elif 14 <= service < 16:
years = 'Over 14'
elif 16 <= service < 18:
years = 'Over 16'
elif 18 <= service < 20:
years = 'Over 18'
elif 20 <= service < 22:
years = 'Over 20'
elif 22 <= service < 24:
years = 'Over 22'
elif 24 <= service < 26:
years = 'Over 24'
elif 26 <= service < 30:
years = 'Over 26'
elif 30 <= service < 34:
years = 'Over 30'
elif 34 <= service < 38:
years = 'Over 34'
elif 38 <= service <= 40:
years = 'Over 38'
elif service < 0 and service > 40:
print("Service years was entered incorrectly. Must be >0 and <40.")
return years
def calc_pay():
"""Opens Calculator prompt"""
separator = ' '
print("Please enter rank followed by years in service.")
print("Format is 'E-1' space #, ex: 'O-5 20' or 'E-3 6'")
user_input = input(": ").split(separator)
rank = user_input[0]
service = user_input[1]
active_input = input("Active duty, Y? Reserve is N.: ").lower()
if active_input in ("yes", "y"):
active = 1
act_out = 'n active duty '
elif active_input in ("no", "n"):
active = 0
act_out = ' reserve '
else: sys.exit("Please try again with Y or N for active duty answer.")
enlist = str(user_input[0])
if ord(enlist[0]) == ord('E'):
enlisted = 1
else: enlisted = 0
rank = str(rank)
service = int(service)
years = determine_years(service)
pay = find_pay(rank, years, active, enlisted)
if math.isnan(pay):
print("There doesn't exist a base pay for that rank with those years of service")
else: print("The 2017 base pay for a"+act_out+rank+" with "+str(service)+" years of service is: ", '${:,.2f}'.format(pay))
if __name__ == "__main__":
calc_pay()
#!/usr/bin/env python3
"""Calculate 2017 US military service base pay. By Wolf"""
import sys
import math
import pandas as pd
def find_pay(rank, years, active, enlisted):
"""Returns cell value given rank, years, duty status, enlisted status"""
if active == 1:
if enlisted == 1:
ae_read = pd.read_csv('E_2017pay.csv')
ae1 = pd.DataFrame(ae_read)
a_e = ae1.set_index('Years')
pay = a_e.loc[years, rank]
return pay
elif enlisted == 0:
ao_read = pd.read_csv('O_2017pay.csv')
ao1 = pd.DataFrame(ao_read)
a_o = ao1.set_index('Years')
pay = a_o.loc[years, rank]
return pay
elif active == 0:
if enlisted == 1:
re_read = pd.read_csv('RE_2017pay.csv')
re1 = pd.DataFrame(re_read)
r_e = re1.set_index('Years')
pay = r_e.loc[years, rank]
return pay
elif enlisted == 0:
ro_read = pd.read_csv('RO_2017pay.csv')
ro1 = pd.DataFrame(ro_read)
r_o = ro1.set_index('Years')
pay = r_o.loc[years, rank]
return pay
def determine_years(service):
"""Takes years of service, choose experience range assigns to years"""
if 0 <= service < 2:
years = 'Under 2'
elif 2 <= service < 3:
years = 'Over 2'
elif 3 <= service < 4:
years = 'Over 3'
elif 4 <= service < 6:
years = 'Over 4'
elif 6 <= service < 8:
years = 'Over 6'
elif 8 <= service < 10:
years = 'Over 8'
elif 10 <= service < 12:
years = 'Over 10'
elif 12 <= service < 14:
years = 'Over 12'
elif 14 <= service < 16:
years = 'Over 14'
elif 16 <= service < 18:
years = 'Over 16'
elif 18 <= service < 20:
years = 'Over 18'
elif 20 <= service < 22:
years = 'Over 20'
elif 22 <= service < 24:
years = 'Over 22'
elif 24 <= service < 26:
years = 'Over 24'
elif 26 <= service < 30:
years = 'Over 26'
elif 30 <= service < 34:
years = 'Over 30'
elif 34 <= service < 38:
years = 'Over 34'
elif 38 <= service <= 40:
years = 'Over 38'
elif service < 0 and service > 40:
print("Service years was entered incorrectly. Must be >0 and <40.")
return years
def calc_pay():
"""Opens Calculator prompt"""
separator = ' '
print("Please enter rank followed by years in service.")
print("Format is 'E-1' space #, ex: 'O-5 20' or 'E-3 6'")
user_input = input(": ").split(separator)
rank = user_input[0]
service = user_input[1]
active_input = input("Active duty, Y? Reserve is N.: ").lower()
if active_input in ("yes", "y"):
active = 1
act_out = 'n active duty '
elif active_input in ("no", "n"):
active = 0
act_out = ' reserve '
else: sys.exit("Please try again with Y or N for active duty answer.")
enlist = str(user_input[0])
if ord(enlist[0]) == ord('E'):
enlisted = 1
else: enlisted = 0
rank = str(rank)
service = int(service)
years = determine_years(service)
pay = find_pay(rank, years, active, enlisted)
if math.isnan(pay):
print("There doesn't exist a base pay for that rank with those years of service")
else: print("The 2017 base pay for a"+act_out+rank+" with "+str(service)+" years of service is: ", '${:,.2f}'.format(pay))
if __name__ == "__main__":
calc_pay()
Simulation
Now that my basic program is converted to Python 3 I can start playing with it for the purpose of this project.
As a reminder the ultimate goal is to analyze cost savings of the US military's implementation of the new Blended Retirement System.
I decided to create a simulator and it is here I am running into disappointment. The goal is to create a simulator that very closely mirrors reality with accurate distributions in costs as calculated by numbers in each rank and pay rate.
Here is my simulator code so far:
#!/usr/bin/env python3
"""Calculate 2017 US military service base pay. By Wolf"""
import math
import random
import pandas as pd
import numpy as np
def find_pay(rank, years, active, enlisted):
"""Returns cell value given rank, years, duty status, enlisted status"""
if active == 1:
if enlisted == 1:
ae_read = pd.read_csv('E_2017pay.csv')
ae1 = pd.DataFrame(ae_read)
a_e = ae1.set_index('Years')
pay = a_e.loc[years, rank]
return pay
elif enlisted == 0:
ao_read = pd.read_csv('O_2017pay.csv')
ao1 = pd.DataFrame(ao_read)
a_o = ao1.set_index('Years')
pay = a_o.loc[years, rank]
return pay
elif active == 0:
if enlisted == 1:
re_read = pd.read_csv('RE_2017pay.csv')
re1 = pd.DataFrame(re_read)
r_e = re1.set_index('Years')
pay = r_e.loc[years, rank]
return pay
elif enlisted == 0:
ro_read = pd.read_csv('RO_2017pay.csv')
ro1 = pd.DataFrame(ro_read)
r_o = ro1.set_index('Years')
pay = r_o.loc[years, rank]
return pay
def determine_years(service):
"""Takes years of service, choose experience range assigns to years"""
if 0 <= service < 2:
years = 'Under 2'
elif 2 <= service < 3:
years = 'Over 2'
elif 3 <= service < 4:
years = 'Over 3'
elif 4 <= service < 6:
years = 'Over 4'
elif 6 <= service < 8:
years = 'Over 6'
elif 8 <= service < 10:
years = 'Over 8'
elif 10 <= service < 12:
years = 'Over 10'
elif 12 <= service < 14:
years = 'Over 12'
elif 14 <= service < 16:
years = 'Over 14'
elif 16 <= service < 18:
years = 'Over 16'
elif 18 <= service < 20:
years = 'Over 18'
elif 20 <= service < 22:
years = 'Over 20'
elif 22 <= service < 24:
years = 'Over 22'
elif 24 <= service < 26:
years = 'Over 24'
elif 26 <= service < 30:
years = 'Over 26'
elif 30 <= service < 34:
years = 'Over 30'
elif 34 <= service < 38:
years = 'Over 34'
elif 38 <= service <= 40:
years = 'Over 38'
elif service < 0 and service > 40:
print("Service years was entered incorrectly. Must be >0 and <40.")
return years
def calc_pay():
"""Opens Calculator prompt"""
active = random.randrange(2)
enlisted = random.randrange(2)
rank1 = pd.read_csv('usarmy2017.csv')
rank_df = pd.DataFrame(rank1)
rank_list = rank_df['rank']
percent_list = rank_df['percent']
rank = np.random.choice(rank_list, p=percent_list)
min_year = (rank_df[rank_list == rank]).iloc[0, 3]
service = random.randrange(min_year, 41)
years = determine_years(service)
if ord(rank[0]) == ord('E'):
enlisted = 1
else: enlisted = 0
pay = find_pay(rank, years, active, enlisted)
if active == 1:
act_out = 'n active '
else: act_out = ' reserve '
if math.isnan(pay):
print("There doesn't exist a base pay for a "+rank+" with "+str(service)+" years of service.")
else: print("The 2017 base pay for a"+act_out+rank+" with "+str(service)+" years of service is: ", '${:,.2f}'.format(pay))
if __name__ == "__main__":
calc_pay()
You'll see I have eliminated user inputs, now the inputs are randomized with appropriate possible inputs. This program is still producing just one data point so far.
To base my simulator in reality I am weighing the randomness by real world amounts by accessing a csv file with numpy.random.choice().
This selects a random rank based on its weighted distribution provided in the chart. You can view the chart here. Please note I have decided to narrow my development to only look at US army ranks for now. This simulator should eventually be able to work for all services and eventually return any year analysis. That's going to be a lot of csv tables! Or just redesign to operate off a database, right now I am working with PostGreSQL in other projects.
Notice the first weight is provided in exponent notation which is well interpreted by NumPy. However you likely noticed the last column minyears. This column provides the commonly understood minimum number of years of service to achieve the corresponding rank. It's not a perfect science since there are performance factors involved, and it also breaks down in the upper officer ranks where they aren't clear rules. What I have created however is a lower bounds for my service years.
Where now?
At this point my data is starting to become imperfect and I need to pause and analyze how far I can take this simulator. I have a perfect distribution for rank. I can also create perfect distributions for questions of enlisted/officer or active/reserve. That would create a perfect simulation, but unfortunately I need to have the crucial input of service years.
The only way to fix this I see is to find data of experience by rank. I've been digging through actuary.defense.gov to see their yearly reports, but haven't found something this exact yet.
So I am naturally going to take this two ways at once, keep digging for real numbers, but also begin constructing a solve-for that could populate this for me using the total costs against all the known inputs I have so far. The only variable missing is a statistical distribution of service by rank. If I create this I can then achieve a most perfect simulator.
However this also makes me want to work on other projects.
Comments
There are no comments yet.