Goals

The goal of this assignment is to work with scripts and packages in Python.

Instructions

You will be doing your work in Python for this assignment. You may choose to work on this assignment on a hosted environment (e.g. tiger) or on your own local installation of Jupyter and Python. You should use Python 3.8 or higher for your work. To use tiger, use the credentials you received. If you work remotely, make sure to download the .py files to turn in. If you choose to work locally, Anaconda is the easiest way to install and manage Python. If you work locally, you may launch Jupyter Lab either from the Navigator application or via the command-line as jupyter-lab.

In this assignment, we will again be working with data from Pokémon compiled from online sources. As with Assignment 3, we will be using the Complete Pokemon Dataset collected by Mario Tormo Romero from online sources. Rather than using this dataset directly, I have created a subset of this data, which can be read into a list of dictionaries. That data is located here; it is updated from Assignment 3 with the addition of sp_attack and sp_defense so download the new one. Once loaded, the data is a list of dictionaries where each dictionary has 12 key-value pairs. Those keys and a brief description are:

  • name: the name
  • generation: the generation (1-8)
  • species: the species (e.g. Lizard Pokémon)
  • primary_type: the primary type (e.g. Grass, Fire)
  • hp: base hit points
  • height_m: height in meters
  • weight_kg: weight in kilograms
  • speed: base speed
  • attack: base attack
  • defense: base defense
  • sp_attack: special attack
  • sp_defense: special defense

You will be writing Python modules, putting them in a package, and writing a script to help analyze this data. You may use other core Python modules (e.g. collections) in this assignment.

Due Date

The assignment is due at 11:59pm on Friday, March 5.

Submission

You should submit the completed Python files required for this assignment on Blackboard. Zip the files together; the filename of the zipfile should be a5.zip. You can create an archive on tiger (assuming at a directory above the package) using the following code:

import shutil
shutil.make_archive('a5', 'zip', '.', 'pokemon_analysis')

Then, download the a5.zip to turn in via Blackboard.

Details

Please make sure to follow instructions to receive full credit. To test your code, you may use the %run magic command in the notebook. For example,

%run pokemon.py 
%run -m pokemon_analysis

You may also use the Terminal in Jupyter on tiger, but you should activate the correct environment by first running:

$ conda activate py38

0. Name & Z-ID (5 pts)

Since we are using Python files (.py) files for this assignment, add the identifying information to the beginning of your __main__.py script and the __init__.py file of your package. Minimally, you should have a line for your name and a line for your Z-ID. If you wish to add other information (the assignment name, a description of the assignment), you may do so after these two lines.

1. Pokémon Analysis Package (50 pts)

Create three new Python modules, one for reading the dataset, one for analyzing generations, and one for comparing two Pokémon characters. Put the three modules (data.py, generation.py, and compare.py) into a package named pokemon_analysis.

1a. Read Data (10 pts)

Create a data.py module that has a get_data method that reads and parses the pokemon.json datafile, and stores it in a module variable. Assume that the data file resides in the same directory as data.py. You can then get its absolute path via the __file__ variable of the module via:

import os
fname = os.path.join(os.path.dirname(__file__),'pokemon.json')

Use the json module to load the data from the file. Your get_data method should only read the file from disk once, otherwise returning the pre-loaded data.

Hints
  • Initialize the module variable to a sentinel value to indicate when the data has not been read.
  • You can use %autoreload to automatically reload modules as you edit them. Do note, however, that this will mask the effects of trying to not keep reloading the data! You can also use importlib.reload to do this manually.

1b. Generation Analysis (15 pts)

Create a generation.py module that has two methods that both take one parameter, the generation number. Use the get_data method from the data module to obtain the data. The first method, generation_types, should return a dictionary of the form {<primary_type>: <count>} with the counts of primary types for the given generation. The second method, generation_ranges, should return a dictionary of the form {<measure>: (<min_value>,<max_value)} with the minimum and maximum values for hp, attack, and defense, among all pokemon in that generation.

Hints
  • Make sure to import the data module! You might consider using relative imports to do this from a sibling module.
  • You may use collections.Counter for generation_types.
  • You could consider using collections.defaultdict to help with generation_ranges.

1c. Comparison (20 pts)

Create a compare.py module calculates comparative information between two Pokemon. Again, use the get_data method from the data module to obtain the data. Given two names as parameters, the attack_diff, defense_diff, and hp_diff methods should return the difference as a float between the two pokemons in the representative values (the first minus the second). Do this remembering the DRY principle! Finally, the combat_power_diff method will comapre the combat power of two pokemons, again returning the difference as a float. The combat power can be computed via information here as (note: there was a typo, now fixed, in the formula until 2021-02-28):

Attack = 2 * round(attack^0.5 * sp_attack^0.5 + speed^0.5)
Defense = 2 * round(defense^0.5 * sp_defense^0.5 + speed^0.5)
Stamina = 2 * hp
MaxCP = (Attack + 15) * (Defense + 15)^0.5 * (Stamina + 15)^0.5 * 0.7903001^2 / 10
Hints
  • Consider creating another function that attack_diff, defense_diff, and hp_diff, can all use.
  • Consider testing the functions via code in the modules (but remember to make sure they only run when the module is run as a script.)

1d. Package (5 pts)

Make sure all three analysis modules live in a single pokemon_analysis package. Add an __init__.py file for completeness. It may contain documentation and the pass keyword.

2. Pokémon Analysis Script (25 pts)

Add a __main__.py file that allows the package to embed a top-level script. That script should process two subcommands; the first is “generation” and the second is “compare”. The first subcommand takes a generation number as an argument and prints the result from the generation_range (fixed typo, 2021-03-02) method, and the second subcommand takes the names of two Pokémon as arguments and prints the result from the combat_power_diff method. You can test your script via the IPython magic command %run -m pokemon_analysis ... or via the shell command !/opt/conda/envs/py38/bin/python -m pokemon_analysis ... (you will need to adjust the path if not using tiger). Note that for %run, you will need to make sure the package also has an __init__.py file (a good habit anyway). Make sure to print a usage method if the user misses or provides incorrect arguments. Some sample output:

%run -m pokemon_analysis
Usage: python -m pokemon_analysis [generation <num> | compare <name1> <name2>]

%run -m pokemon_analysis generation
Usage: python -m pokemon_analysis [generation <num> | compare <name1> <name2>]

%run -m pokemon_analysis generation 3
Generation 3:
  hp: 1.0-170.0
  attack: 15.0-180.0
  defense: 20.0-230.0

%run -m pokemon_analysis compare Mewtwo Magikarp
Mewtwo has +3882.048635 combat power than Magikarp

%run -m pokemon_analysis compare Mewtwo Slaking
Mewtwo has -227.238909 combat power than Slaking
Hints
  • Create a usage method that can be called whenever there is trouble
  • You will need a different number of arguments depending on which subcommand is called
  • Look at the format language to display + or - appropriately for the compare subcommand

Extra Credit

  • Extend the main script to allow use of the other functions in the modules.
  • Extend the compare code and the script to take an arbitrary number (>= 2) of Pokémon as arguments.