Details

Due date

This assignment is due on Monday, May. 13th, at 11:30 PM.

Coverage

This assignment covers the material up to Week 6, focusing on data visualization with Matplotlib's pyplot library.

matplotlib.pyplot

This assignment involves programming with matplotlib.pyplot. You can find all of the material needed to work with matplotlib in Week 5/6 materials (the most useful resource will be the Matplotlib slide deck and lecture code done in class).

Do not use any other functions/modules that we haven't covered in your Part A and Part B solutions. For example, pandas and numpy are not allowed in Part A or B, though you may see examples in Matplotlib's documentation (which is why we provide everything you need in the lectures and posted readings). You are more than welcome to explore these in Part C though!

What to hand in

You will be submitting 3 files to CodePost for this assignment:

  • mp5_parta.py
  • mp5_partb.py
  • mp5_partc.py (and any datasets used in this program, as well as a .png file of your graph(s), e.g. in the name partc_graph.png)

Part C is a small creative component where you will choose a data set and question of interest, and plot a graph with 1) two or more Axes or 2) a different type of plot. You may use any dataset of interest, including lecture datasets (when we introduced csv-processing), lab datasets, your own bottle rocket trial data CSV, planets.csv, or any other datasets you find. More details are provided in the Part C Overview.

Showcase: We encourage students to showcase their creations with the class! You can opt-in to the Creative Showcase (anonymous or non-anonymous) here. Don't worry about perfection/using anything beyond what we've taught; we'd like to showcase a variety of different ideas, data science questions, creativity, etc!

Supporting files

You will be finishing Part A and Part B using the provided starting code, and we have also given you a starter template for Part C:

We have also provided a single utility function used in Part B, factored out in a provided mp5_utils.py, as well as a dataset you will be visualizing in Part B. Both of these should be located in the same directory as your solutions.

While you are not to use features we haven't covered in Part A/B, there is a small case motivating numpy's arange function in the generate_rocket_positions function for both parts. This function is a special version of range that has better support for non-int range generation (recall that the built-in range function we've been using requires int values). Since our rocket launch data is best visualized with a smoother line, we want to support float values in the generated ranges, so you'll see this used once per file. You are not to modify it, but will want to make sure you have numpy installed for your programs to work (and it's a good library to have installed for later data-processing projects as well!).

To install everything you need (we recommend at this step in your reading, before starting the project), enter the following in the bash command (similar to what you've been doing whenever using pip3, ignoring the $ prompt or whatever your system uses):

$ pip3 install matplotlib
$ pip3 install numpy

Or:

$ python3 -m pip3 install matplotlib
$ python3 -m pip3 install numpy

Once you do this, restart VSCode just in case to make sure they are installed.

Program Overview: Plotting Bottle Rocket Launches with Matplotlib

Your tasks will be broken down into 3 Parts. In Part A, you will implement some basic functions to calculate data that is needed to plot a bottle rocket trajectory using gravity, initial velocity, and angle of launch.

Part A will fix these three experimental variables as program constants (indicated with UPPER_CASE conventions). We have given you most of the plotting code for this part, which you will modify for varying trial data in Part B.

You can see a preview of what you're implementing in Part A below:

MP5 Part A Output

In Part B, you will take your functions from Part A and parameterize them to replace the three data-dependent program constants. This is an exercise of refactoring and motivating trade-offs between program constants and parameters. Part B (and C) will also give you opportunities to practice using strategies discussed in recent lectures, including "reverse engineering" matplotlib examples, experimentation, and VSCode debugging.

Whereas in Part A you will plot a single trajectory using the fixed program constants, in Part B you will use your parameterized functions and modified plotting code to plot data for 4 trials with different bottle rocket launch variables.

You can see a preview of what you're implementing in Part B below:

MP5 Part A Output

Finally, Part C will involve exploring another data visualization approach, either 1) plotting data on two or more plots (Axes) instead of one or 2) using a different type of plot (bar, histogram, pie, heatmap, etc.). This part is introduced as an opportunity to apply what you've been learning in a creative way, starting with a data set of interest and a question you want to ask about the data that can be visualized using Matplotlib.

To summarize, all the plots you will be seeing should belong to a single Figure (per program) which has:

  1. Part A: 1 Axes, 1 Line with 2 Points
  2. Part B: 1 Axes, 4 Lines (2 Points per Line)
  3. Part C: Option 1 (two or more Axes, at least one plot each) or Option 2: Using different plot types (bar, histogram, scatterplot, pie, etc.)

Part A: A Simple Bottle Rocket Launch Plot

Summary

We have given you starter code for Part A in mp5_parta.py you will note three program constants at the top of the program:

GRAVITY = 9.81          # m/s^2
ANGLE_OF_LAUNCH = 45    # in degrees from horizon
INITIAL_VELOCITY = 150  # m/s, fixing the initial velocity v0

You will also see some constants that will not be involved in parameterization; these will be unchanged, but demonstrate good use of constants for program-wide settings, especially when doing graphics programming.

# Offsets for point labels (e.g. max height and landing point annotations)
X_LABEL_OFFSET = 150 
Y_LABEL_OFFSET = 10

# Default marker size for plotted points
MARKER_SIZE = 8
LINE_WIDTH = 2

Note that the two offsets could alternatively be implemented as factors (e.g. 5% to generalize the label positioning based on axis scales, but to make it easier to test without floating point subtleties, you will use these constant offsets for Parts A and B (you can choose your own implementation in Part C).

Note that we could also parameterize launch starting positions, but we will not add that support in MP5. You can assume that every launch in Part A/B starts at the position (0, 0), which we've noted in the starter code for you (this will also simplify edge cases with quadratic equations that could otherwise have two solutions).

Final reminders for getting started:

  • Make sure you think carefully about the DRY principle in this assignment! We are looking for you to not only learn how to use Matplotlib, but also to (continue to) think about how to use functions you've already implemented to avoid redundancy, as well as being able to identify the trade-offs between Part A and Part B.
  • Most of the solutions are fairly short, especially since you are finishing partially-completed solutions. Most of the time spent should be on understanding the problem, utilizing the VSCode debugger when you're stuck on common bugs (usually relating to data types of variables at each step), and referring to course materials (e.g. the lecture slides and notes) to piece things together.
  • Use the program constants in Part A as given! Some will then be replaced in Part B as function parameters.
  • Don't forget to refer to Part A when working through Part B.
  • Test iteratively! Once you finish a function, test it in the interpreter without plotting to make sure you get the expected results for valid arguments. Make sure the data types match the spec!
  • Don't worry about the np.arange function more than commented; you are not using numpy anywhere else in Parts A/B.
  • Have fun!

1. calculate_velocity

[10]

First, you'll write a function called calculate_velocity which takes no arguments, but uses the program constants to calculate the vx and vy velocities of the rocket based on an initial velocity, a launch angle (e.g. 45 for 45 degrees from the horizon). The function should return a tuple representing the velocity vector of vxi and vyi float values, which represent the velocities in the same units as INITIAL_VELOCITY.

To calculate velocity, remember from your physics fundamentals to use cos (for x velocity) and sin (for y velocity). We have already imported the math module for you in the starter code for these functions. Since the ANGLE_OF_LAUNCH is in degrees (this makes it easier for users to specify angles), you'll need to convert to radians. You can convert degrees to radians easily with math.radians(<degrees>).

math.radians(0)   # 0.0
math.radians(360) # 6.283... (2pi)
math.radians(90)  # 1.5707...(pi/2)

Using the provided program constants, a call to calculate_velocity() should return the tuple (106.06601717798213, 106.06601717798212) (what happens when you change the INITIAL_VELOCITY and ANGLE_OF_LAUNCH constants?).

For reference, our solution is 5-6 lines ignoring comments. Make sure to use variables to avoid recomputation!

2. calculate_position

[5]

Next, you'll use your calculate_velocity function to implement calculate_position. This function takes a single parameter dt representing the time elapsed, and returns the (x, y) position (a tuple) of the rocket at dt. We have given you the launch starting position, which we will fix at (0, 0) to keep things simple.

Here are some example calls and the expected results for the provided program constants constants (again, these constants should be used in this function):

>>> from mp5_parta import calculate_position
>>> calculate_position(0)
(0.0, 0.0) # Remember that this is the evaluation, not any unspecified print output
>>> calculate_position(1)
(106.06601717798213, 101.16101717798212)
>>> calculate_position(10)
(1060.6601717798212, 570.1601717798212)
>>> calculate_position(21.62406058674457)
(2293.577981651376, 0.0)

If you change ANGLE_OF_LAUNCH to 90, observe the difference (again, remember to change it back to the provided constants in your solution):

>>> from mp5_parta import calculate_position # after experimenting with change
>>> calculate_position(0)
(0.0, 0.0)
>>> calculate_position(10)
(9.184850993605149e-14, 1009.5)

If you then change back ANGLE_OF_LAUNCH to 45, and change INITIAL_VELOCITY from 150 to 10, note the difference:

>>> from mp5_parta import calculate_position # after experimenting with change
>>> calculate_position(0)
(0.0, 0.0)
>>> calculate_position(10)
(70.71067811865476, -419.78932188134524)

We can observe that at 10 seconds into the launch, the y position would be below ground level (why?). What do you think predict will happen if you change the GRAVITY constant?

Already, we can see some of the limitations with program constants in this case. When we're exploring the results of changing different variables, supporting parameters for these will be very useful! But for Part A, our goal is to just get started plotting a simple example with these variables fixed.

For reference, our solution is 5-6 lines ignoring comments.

3. flight_time

[5]

One of the important measurements of a bottle rocket launch is the total time of flight (sometimes referred to TOF). Finish this function to compute the total flight time (which will be in the same units as INITIAL_VELOCITY and GRAVITY). Again, we are assuming the default starting position of (0, 0).

To calculate the total flight time, you'll need to figure out the initial y velocity (use your calculate_velocity function) and the GRAVITY constant. Remember that velocities and gravity in this program have compatible units, both involving seconds. Refer to your physics classwork to determine the total flight time from this information. The time should be returned as a float. For the current program constants, you should expect a call to flight_time() to be 21.62406058674457.

For reference, our solution is 2-3 lines of code and the only constant it references directly is GRAVITY.

4. generate_rocket_positions and 5. plot_labels

Next, you'll add specified comments to A.4. and A.5 and modify a single line of code in A.6. We recommend you do A.6 before you attempt A.5 so that your VSCode debugger works properly. The rest of the functions are given to you in Part A, and you are expected to spend 3-5 minutes understanding what they do in the full program execution (we encourage you to use the VSCode debugger). You will modify some of these in Part B using your parameterized functions.

Here's a brief summary of A.4. and A.5. to get you started:

  • generate_rocket_positions uses the functions you've implemented so far to generate the data for your launch given a total flight time.
  • plot_labels factors out the plotting of the 2 points of interest for each launch: the coordinates of the peak of the launch, and the coordinates of the landing point. This function takes a single Axes argument ax. Remember that ax is an Axes object generated from (fig, ax) = plt.subplots() (see the provided start function) which is used to plot visualizations on a main Figure.

For A.4. and A.5., you will demonstrate your understanding of the provided function through the following requirements (these are only required for the versions in Part A).

4. generate_rocket_positions

[10]

Write a docstring for this function that describes the behavior of the function given the single parameter, including what is returned (do not copy/paste what we wrote above). Follow the same conventions as we've shown in class, including:

  • Omitting any implementation details, like loops and any local variables (including local variables that are returned, but you should specify the names of function parameter when describing them)
  • Specifying the types of the parameter and return
  • Any preconditions/edge cases that the function requires (you don't need to change the implementation; use your docstring to specify any preconditions)

Hint: The most important thing to make sure you understand is the relationship between the local xs and ys variables. Are there any notes about their lengths? Is there a relationship between xs[i] and ys[i] in the context of a rocket launch position?

5. plot_labels

[5]

ATTEMPT THIS AFTER YOU FINISH A.6, otherwise your VSCode debugger may not work.

We've given you this docstring, but to make sure you are set up for the rest of the assignment, you are required to add replace the 4 TODO comments in to the body of this function with the corresponding value (as a # comment):

  • The value of dist_label for the Part A data
  • The value of dist_label_coords for the Part A data
  • The value of maxh_label for the Part A data
  • The value of maxh_label_coords for the Part A data

This requirement is less about documentation, but more to make sure you are able to use the VSCode debugger to determine this information. At this point, we definitely expect you to know how to use the debugger, but if you run into any questions, we're happy to help.

For example, if we were to ask you to do the same thing for the code below, we would expect the TODO replacements in the following format:

# TODO 0: Replace this line with the tuple values of (total_dist, last_y)
(total_dist, last_y) = landing_pt
...
# TODO N: Replace this line with the string value of legend_label
legend_label = f'Launch Trajectory ({tof:.1f}s)'

Replacing the TODOs as described:

# (2293.577981651376, 0.0)
(total_dist, last_y) = landing_pt
...
# 'Launch Trajectory (21.6s)'
legend_label = f'Launch Trajectory ({tof:.1f}s)'

Some notes about A.5.

This function uses the ax.annotate method to specify the following arguments used to annotate a point with text (in this example, no arrow is used but the documentation includes examples using arrows). You can find more about the arguments from the official Matplotlib documentation if you're curious.

Here's a quick summary of the annotation arguments we're using:

  • The text is the label.
  • The xy keyword argument is the (x, y) tuple associated with the annotation.
  • The xytext keyword argument is the (xt, yt) tuple associated with the text of the annotated point. Note that the two offset constants are used to avoid overlapping the text with the annotated point.
  • The color of the annotation is the same as the plotted point.
  • The horizontalalignment keyword is centered so that we can standardize placement of labels.

In addition to plotting the point and its annotation, you'll see ax.set_xlim and ax.set_ylim to set the x and y axis ranges, respectively. Why do we set these? If you experiment commenting out these lines, you'll notice that the landing point marker will be cropped off at y = 0, and the annotation label above the highest point will be cropped off above the upper-bounds of the plot.

6. plot_launch

[5]

The last step in Part A is to add a single line in plot_launch to plot the launch trajectory!

This is the main function for plotting the launch given the three program constants (which you will parameterize in Part B) and plotting configurations.

Use the comments as well as an understanding of your functions so far to familiarize yourself with the plotting code.

Replace the TODO in this function to plot the line for the trajectory, following the instructions in the starter code. The replaced comment/code should only be two lines (a single statement broken into 2 lines to keep the line under 80 characters). Now that you have completed the function, you can go back and fill in the TODO comments from A.5 using the VSCode debugger.

The Rest (Provided)

The rest of the code is given to you; there is nothing for you to add (code or documentation-wise). We've summarized these functions briefly for you.

configure_plot

As mentioned in lecture, our primary source of interaction with Matplotlib is the Axes object(s) returned from plt.subplots(). When you work on data visualization programs, there is more than just plotting your data, and usually there are configurations you will want to override to improve the quality/interpretability of your visualization(s).

This function factors out the configurations specific to the the Figure and Axes and unrelated to the actual lines/points. You'll note that we plot two black points to add to the legend; we use black ('k') to generalize the legend markers for larger visualizations like Part B so we don't have so many legend items.

You'll also see the x and y limits updated here to give some padding to the Figure for the points and annotations to fit. How would one choose these values? Well, in practice you'll want to be considerate of the likelihood of your x and y axis ranges changing (e.g. and an offset of 100 looks much different when the upper-bound is 50 vs. 1000). With more experience, one would be encouraged to use relative offsets with percentages, but for the scope of this assignment we just give you constant values. You are welcome to keep these as-is or be a bit more clever in Part C.

start

Finally, we see the starting point for everything working together! Just like in previous assignments/labs/lecture code, having a short start function is good practice to have good program decomposition. This is where we create the Figure and Axes, and pass ax to any functions that need it (fig isn't used in any other functions, so it shouldn't be passed anywhere).

Part B: Parameterizing Launch Variables

1. calculate_velocity

[5]

First, you'll modify calculate_velocity to take two arguments (vi and launch_angle) to remove the dependency on the respective program constants. This parameterization will allow us to generalize the functions to plot different graphs in Part B to compare the results of changing different experimental variables.

As with Exercise A.1., the function should still return a tuple representing the velocity vectors vx and vy, and should treat the parameters the same (e.g. launch_angle is still expected to be in degrees, vi is expected to be compatible with gravity units).

For reference, your solution should be the same length as A.1 and should return the same result when called with INITIAL_VELOCITY, ANGLE_OF_LAUNCH.

Here are some example calls and expected results of your parameterized implementation:

>>> import mp5_parta # avoid naming conflict
>>> mp5_parta.calculate_velocity() # non-parameterized version
(106.06601717798213, 106.06601717798212)
>>> from mp5_partb import calculate_velocity
>>> INITIAL_VELOCITY = 150 # m/s, sanity check with Part A values
>>> ANGLE_OF_LAUNCH = 45   # in degrees from horizon
>>> calculate_velocity(INITIAL_VELOCITY, ANGLE_OF_LAUNCH)  
(106.06601717798213, 106.06601717798212)
>>> calculate_velocity(150, 90)
(9.184850993605149e-15, 150.0)
>>> calculate_velocity(150, 60)
(75.00000000000001, 129.9038105676658) # floating point arithmetic isn't perfect 
>>> calculate_velocity(150, 30)
(129.9038105676658, 74.99999999999999)
>>> calculate_velocity(100, 45)
(70.71067811865476, 70.71067811865474)
>>> calculate_velocity(10, 45)
(7.0710678118654755, 7.071067811865475)
>>> calculate_velocity(0, 10)
(0.0, 0.0)
>>> calculate_velocity(10, 0)
(10.0, 0.0)

2. calculate_position

[5]

Next, you'll modify calculate_position to take three additional arguments, replacing any occurrences of global constants with the corresponding parameter values. The initial position should still be set at (0, 0).

The following are example calls and expected returns, starting with the examples shown from Part A.2.

>>> from mp5_partb import calculate_position
>>> INITIAL_VELOCITY = 150 # m/s, sanity check with Part A values
>>> ANGLE_OF_LAUNCH = 45   # in degrees from horizon
>>> GRAVITY = 9.81         # m/s^2
>>> calculate_position(0, INITIAL_VELOCITY, ANGLE_OF_LAUNCH, GRAVITY) 
(0.0, 0.0) # We haven't moved at t=0
>>> calculate_position(1, INITIAL_VELOCITY, ANGLE_OF_LAUNCH, GRAVITY) 
(106.06601717798213, 101.16101717798212)
>>> calculate_position(10, INITIAL_VELOCITY, ANGLE_OF_LAUNCH, GRAVITY) 
(1060.06601717798213, 570.16017)
>>> # Testing the time-of-flight from Part A
>>> calculate_position(21.62406058674457, INITIAL_VELOCITY, ANGLE_OF_LAUNCH, GRAVITY)
(2293.577981651376, 0.0)

>>> # Experimenting with some variables
>>> # Changing velocity at dt=1
>>> calculate_position(1, 100, ANGLE_OF_LAUNCH, GRAVITY) 
(70.71067811865476, 65.80567811865474)
>>> calculate_position(1, 50, ANGLE_OF_LAUNCH, GRAVITY) 
(35.35533905932738, 30.45033905932737)
>>> calculate_position(1, 1, ANGLE_OF_LAUNCH, GRAVITY) 

(0.7071067811865476, -4.197893218813453) # what does this mean?
>>> # Changing launch angle at dt=1 (try other values of dt)
>>> calculate_position(1, INITIAL_VELOCITY, 30, GRAVITY) 
(129.9038105676658, 70.09499999999998)
>>> calculate_position(1, INITIAL_VELOCITY, 60, GRAVITY) 
(75.00000000000001, 124.9988105676658)
>>> calculate_position(10, INITIAL_VELOCITY, 60, GRAVITY) 
(750.0000000000001, 808.538105676658)

>>> # Changing gravity constant 
>>> calculate_position(1, INITIAL_VELOCITY, ANGLE_OF_LAUNCH, 1) 
(106.06601717798213, 105.56601717798212)
>>> calculate_position(1, INITIAL_VELOCITY, ANGLE_OF_LAUNCH, 100)
(106.06601717798213, 56.06601717798212)
>>> calculate_position(10, INITIAL_VELOCITY, ANGLE_OF_LAUNCH, 1)
(1060.6601717798212, 1010.6601717798212)
>>> calculate_position(10, INITIAL_VELOCITY, ANGLE_OF_LAUNCH, 100)
(1060.6601717798212, -3939.339828220179) # what does this mean?

3. flight_time

[5]

Next, modify flight_time to take three additional parameters, replacing any occurrences of global constants with the corresponding parameter values. The launch_angle variable should be treated as degrees, just like in Part A. Again, A.3. and B.3. should still have the same number of lines.

>>> from mp5_partb import flight_time
>>> # Testing with Part A's constant values
>>> INITIAL_VELOCITY = 150 # m/s, sanity check with Part A values
>>> ANGLE_OF_LAUNCH = 45   # in degrees from horizon
>>> GRAVITY = 9.81         # m/s^2
>>> flight_time(INITIAL_VELOCITY, ANGLE_OF_LAUNCH, GRAVITY)
21.62406058674457
>>> # A few more examples
>>> flight_time(1, ANGLE_OF_LAUNCH, GRAVITY)
0.14416040391163046
>>> flight_time(5, ANGLE_OF_LAUNCH, GRAVITY)
0.7208020195581524
>>> flight_time(500, ANGLE_OF_LAUNCH, GRAVITY)
72.08020195581523
>>> flight_time(5000, ANGLE_OF_LAUNCH, GRAVITY)
720.8020195581523
>>> flight_time(INITIAL_VELOCITY, 30, GRAVITY)
15.290519877675838
>>> flight_time(INITIAL_VELOCITY, 60, GRAVITY)
26.483957302276412
>>> flight_time(INITIAL_VELOCITY, ANGLE_OF_LAUNCH, 1)
212.13203435596424
>>> flight_time(INITIAL_VELOCITY, ANGLE_OF_LAUNCH, 50)
4.242640687119285
>>> flight_time(INITIAL_VELOCITY, ANGLE_OF_LAUNCH, 100)
2.1213203435596424

4. generate_rocket_positions

[5]

Next, you'll update generate_rocket_positions from Part A to use your parameterized functions. This version takes three additional parameters of interest for determining positions of a rocket during its launch. Use these parameters appropriately to call your parameterized Part B functions.

The function should still return a single (list, list) tuple representing the information specified in Part A.

>>> from mp5_partb import flight_time, generate_rocket_positions
>>> # Testing with program constants from Part A
>>> GRAVITY = 9.81 # m/s^2
>>> ANGLE_OF_LAUNCH = 45 # in degrees from horizon 
>>> INITIAL_VELOCITY = 150 # m/s LOCK THE VELOCITY
>>> from mp5_partb import flight_time
>>> # Note: there's a trade-off here between calling flight_time in generate_rocket_data
>>> # since we can compute it from the other variables, but the given 
>>> # approach will be a bit more efficient/generalizable if we want to generate partial data
>>> tof = flight_time(INITIAL_VELOCITY, ANGLE_OF_LAUNCH, GRAVITY)
>>> tof
21.62406058674457
>>> xs, ys = generate_rocket_positions(INITIAL_VELOCITY, ANGLE_OF_LAUNCH, GRAVITY, tof)
>>> len(xs)
218 # based on step size in function
>>> len(ys)
218
>>> xs[0] # initial x
0.0
>>> ys[0] # initial y
0.0
>>> xs[1]
10.606601717798213
>>> ys[1]
10.557551717798214 # why is y smaller for a 45deg launch?
>>> xs[-1]
2293.577981651376 # total distance traveled
>>> ys[-1]
0.0 # back to ground

5. plot_trial_data

[10]

Finish the following TODOs in plot_trial_data to generate line graphs and max height/max distance labels for each trial. Remove each TODO comment when you are done.

  • Use your generate_rocket_positions to generate the x and y lists for the launch coordinates using the appropriate parameters.
  • Determine the highest (x, y) point on the chart using your calculate_position function. Remember that the peak of the trajectory (without resistance/etc.) is exactly at the middle of the launch time.
  • Plot the line with ax.plot, saving the return as lines.
  • Plot the highest point and landing point with labels using plot_labels and the appropriate arguments.

If you correctly implement this function, you should see the result match the graph shown at the start of this specification.

The Rest (Provided)

The rest of the code is given to you; there is nothing for you to add (code or documentation-wise). The functions are similar to that of Part A with a few modifications for the configurations.

Part C: Creative Component

Overview and Requirements

[30-50]

Update: In order to make grading easier for TAs, please include a .png for your graph(s) along with your submission. You can name it partc_graph.png (you can submit multiple if you happen to have multiple features).

In this final part, you will come up with a data science question of interest and create a plotting program that has at least two Axes Write your solution in mp5_partc.py. We have started a general template for you, but you are welcome to modify it (and should remove any unused code). Use helper functions appropriately, referring to Part A and Part B for ideas of how to break down your program.

Data visualization can be very powerful and interesting to dive into, but this part is intended to get you more practice with small extensions of basic plots so far. You are not expected to spend more than 30-40 minutes on Part C, as long as you meet the requirements. One example of something that can be tricky to deal with (which we give to you in Parts A and B) is managing labels and annotations; as long as you appropriately plot your data it's ok if your labels aren't perfectly placed (you can make a comment in your program about room for improvement).

You may use any dataset, including Lab04 Spotify data, Lab05 data, pokedex.csv data, your own generated trial data, etc. We've also provided a small planets.csv dataset that might be of interest to incorporate into MP5.

For full credit, your program should:

  • Replace Data Source: TODO with the dataset you are exploring
  • Replace Data Science Question: TODO with the question you are exploring. Some examples include:
    • "What is the relationship between launch angles and total distance traveled when other launch properties are fixed?
    • "How do bottle rocket launches compare on different planets where gravities are changed?"
    • "What songs are most popular in the CS 1 22fa Staff Spotify Playlist?"
  • Your program should have at least one helper function
  • Extend the simple plots from Part A and Part B with one (or both) of the following modifications:
    1. Using 2 or more Axes. Such a program should use a single Figure and at least two Axes. In other words, use (fig, ax) = plt.subplots(nrows, ncols) where nrows * ncols is >= 2. The simplest implementation would be to have two side-by-side plots with one graph each (see Lecture 17 slides for examples). Don't think too hard about having a "perfect" data visualization; this comes with practice, but this approach gives you experience working with multiple Axes.
    2. Using a plot type that is different from a plotted line or point (e.g. bar chart, histogram, scatterplot, polar grid, etc.)
  • You are welcome to look through examples in Matplotlib's official documentation. You must include a link at the top of your mp5_partc.py program listing all the resources you find (your code should be specific to your dataset/question, and should be at least 50% written by you, and should follow the patterns/conventions taught in CS1). Note that this is different than other assignments, where we do not expect you to refer to external resources. We are giving you some freedom (and trust) here though to practice useful strategies specific to learning a new library's features. You are still expected to follow the Honor Code when it comes to referring to external sources (in this case, Matplotlib documentation/examples).
  • Your program should not have any debugging/print statements when ran, and should generate a single Figure with the above requirements without any errors.
  • You are welcome to use any features in Matplotlib as well as numpy if you'd like to explore it. Note that some TAs will have less experience with Matplotlib's features than others, but you are welcome to ask on Discord if you're curious about how to use different types of plots

Note that we will not be making documentation a priority in Part C (you should at least have brief comments, if not complete docstrings); we still expect you to use reasonable program composition, appropriate use of loops/data types, etc. similar to programs you've implemented so far in CS 1. But we also will not penalize for "perfect use" when exploring other features in Matplotlib. You are welcome to ask questions/get feedback from El during/after your Part C implementation, as TAs will not be giving as thorough feedback on Part C.

Students are encouraged to submit their Part C creations in the #student-plots Discord Channel keeping in mind that some students have prior experience using MATLAB or Matplotlib, so it's ok if yours isn't as advanced as others you might see!