Merge and New Branch
I have decided to leave any further enhancements of the name search facility for the future — if at all. Instead I am going to work on providing percentage based population plots as well as absolute numerical population plots So, I merged the search-names branch with the master branch. Removed the branch locally and remotely. Then created a new branch, percent-pop, for the next set of code development.
Percentage Population
Well, seems to me producing a percentage plot should be simple enough for our first two plot types. We just need to total the age-group populations to get the total population for that year. Then generate and use percentages in the plots. For the many country, many year, one age group plot that is not possible. As for any given year we only have one value. We’ll work on the easy ones first. Then tackle the apparently more difficult situation.
Determining What to Plot: Numeric Count or Percentage
But first, how and where to determine if we are doing plots by age group count or percentage? I thought about adding another query to each plots user interface to ask what type of plot the user wanted. But, that just struck me as too messy. So, I have decided to add an item to the menu that toggles a default value indicating the type of plot. Then use that global value in the functions to determine what values to plot.
So let’s add the new menu item and the related code. You will likely want to let the user know the current state in some fashion. I will include it in the menu item’s text. See you back here when you’re done.
Let’s start by adding the variable and a new menu item. I was originally going to use ‘P’ (i.e. Percent) for the menu item. But then, keeping things alphabetical would have put it after the Plot chart selection. Didn’t think that was the best choice. So, I went with ‘%’ which I figured could safely ignore alphabetical positioning. Thought having it above the Plot chart selection was a logical place for it.
# default state is numeric count for population values in plotted charts
do_percent = False
MENU_M = {
'A': 'About',
'%': 'Toggle percentage plots',
'C': 'Plot chart',
'S': 'Search country/region names',
'X': 'Exit the application'
}
Then we need to add the code to print the menu item. Which has to account for the current state of do_percent. So we had to add an if control block to cover printing the menu option for ‘%’. Note the use of the ternary operator to determine the text based on the current state of the do_percent variable.
for key, value in MENU_M.items():
if key == '%':
print(f"\t\t{CMITEM}{key}{TRESET}: {value} {TRED+'Off'+TRESET if do_percent else TGREEN+'On'+TRESET}")
else:
print(f"\t\t{CMITEM}{key}{TRESET}: {value}")
And finally we need to toggle the do_percent variables state whenever the user selects the ‘%’ menu item. The code I used is a pretty efficient way to toggle a boolean value. And, I believe it is a well understodd programming idiom. You may have chosen to be much more clear in your code. You could of course also use something like do_percent ^= True
, but that might not be as obvious to everyone reading your code.
elif u_choice.upper() == '%':
do_percent = not do_percent
And, it seems to work!
Brain Dead Me!
Whether the columns are representing numerical population totals or percentages, for one age group relative to another the ratio is going to be the same. So the columns for each age group in a given year, given the same size chart, will still be the same height. Talk about slow-witted. Has taken me weeks to realize that bit of truth.
Probably should have tested before getting into this ¿enhancement?. Would have seen that there was no difference at all in the chart presented.
That said, let's finish this refactoring. Comparing percentages between countries and/or years will provide a different perspective.
The affected area of code for the loop looks like (showing the existing line before and after):
dbg_data = pdb.get_1cr_years_all(p_nms[0], yr_list)
if do_percent:
for p_yr, y_data in dbg_data.items():
yr_tot_pop = sum(y_data)
dbg_data[p_yr] = [agp/yr_tot_pop*100 for agp in y_data]
plot_data[1].append(dbg_data)
We also need to change the y-axis text for the percentage case. Other than that the chart looks fine.
if do_percent:
ax.set_ylabel('Population (% Annual Total)')
else:
ax.set_ylabel('Population (1000s)')
Percentage Type 2 Plots
Well, looks to me like we need to pretty much the same thing to get a Type 2 percentage chart. Only real difference is that the key for the dictionary of population data is a country/region name, rather than a year. So, the section of plot_population() for this chart type, I now have:
dbg_data = pdb.get_crs_1yr_all(p_nms, p_yrs[0])
if do_percent:
for p_cr, cr_data in dbg_data.items():
yr_tot_pop = sum(cr_data)
dbg_data[p_cr] = [agp/yr_tot_pop*100 for agp in cr_data]
plot_data[0].append(dbg_data)
And, in plot_m1a(), I now have:
# Add some text for labels, title and custom x-axis tick labels, etc.
if do_percent:
ax.set_ylabel('Population (% Annual Total)')
else:
ax.set_ylabel('Population (1000s)')
ax.set_xlabel('Age Groups')
Done for Now
Getting to be a lengthy post, so think I will call it quits for today. And, the last chart type is going to get somewhat complicated I think. Also, thinking that since the code for the first two chart types is duplicated, it should likely go into a separate function. And, likely should also write a separate function to deal with the Type 3 Chart percentage case rather than including that code in plot_population(). Something to think about for next time.
Note, I don’t think it would make sense to move the duplicated y-label code into a function.
Also, while working on this I was reminded that the charting sub-menu doesn’t exit after each plot. So, if you wanted to switch between population counts and percentages you would need to exit the charting sub-menu to do so. Doesn’t sound very user friendly. So, I will look at moving that menu item from the main menu to the charting sub-menu. Which will mean changing how we sync the variable in the chart module with the one in the main module.
Anyway, until next time. (Still posting on the semi-weekly schedule.)
Resources
- Conditional Expressions ternary operator
- How to toggle a value in Python
- How to make a cross-module variable?
- Explain the visibility of global variables in imported modules in Python?
More Pylint Corrections/Exceptions
I also decided to get rid of the Pylint warnings regarding the two occurrences of a bare-except. Modified code to check for a ValueError on conversion to int and committed the changes. Warnings gone. And, was getting wrong-import-position and unused-variable complaints in the chart.chart.py module. So used Pylint pragmas to disable those messages for now.
I will at some point re-enable the unused-variable message and do something about any remaining unused variables.
- Learn pylint: No exception type(s) specified :: Pylint :: W0702
Pylint Messages Control
PS R:\learn\py_play> git diff percent-pop~1 percent-pop diff --git a/population/chart/chart.py b/population/chart/chart.py index 81e33cc..36c74b1 100644 --- a/population/chart/chart.py +++ b/population/chart/chart.py @@ -14,8 +14,12 @@ if __name__ == '__main__': except ValueError: # Already removed pass +# import here so above code can run when testing module +# pylint: disable=wrong-import-position from database import population as pdb +# pylint: disable=unused-variable + def plot_bar_chart(country, year, pop_data): # define the x-labels for the chart x_labels = pop_data.keys()