The goal of this assignment is to work with scripts and packages in Python.
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.13 for your
work. (Older versions may work, but your code will be checked with
Python 3.13.) To use tiger, use the credentials you received. If you
work remotely, make sure to download the .zip file containing the .py
files to turn in. If you choose to work locally, Anaconda, miniforge, or uv are probably the easiest ways
to install and manage Python. You should be able to run
python or jupyter lab from a shell.
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 namegeneration: the generation (1-8)species: the species (e.g. Lizard Pokémon)primary_type: the primary type (e.g. Grass, Fire)hp: base hit pointsheight_m: height in metersweight_kg: weight in kilogramsspeed: base speedattack: base attackdefense: base defensesp_attack: special attacksp_defense: special defenseYou 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.
The assignment is due at 11:59pm on Monday, October 27.
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.
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 py3.13
If this command produces an error, you may also need to run
$ conda init
to initialize the shell. Open a new terminal after this command
updates your shell startup scripts before running the
activate command again. Once the environment is activated,
you can run your code via:
$ python -m pokemon_analysis
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.
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.
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.
%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.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.
collections.Counter for
generation_types.collections.defaultdict to
help with generation_ranges.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:
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
attack_diff,
defense_diff, and hp_diff, can all use.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.
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 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/py3.10/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