Activity 3-5

Animations!

In this activity, you'll be learning to create more complex, animated visualizations using Plotly.

Task 1: Setting things up

For the first part of this activity, you will be using this stencil code to create an animated graph with circles that change position, size, and color with every frame. It will look similar to this!

First, you will need to upgrade your version of plotly to the latest version. Run the command pip install plotly --upgrade through the command line.

Add the plotly imports to the top of your program code: import plotly.graph_objs as go and from plotly.offline import plot. As a reminder, you can then plot the data by calling plot(figure), which opens an html file in your browser.

We've provided a dictionary called positions, which includes the positions that we want the data points to move to when we animate the graph. Each key refers to the coordinate and frame number. For example, 'y3' refers to the y-axis at frame 3. Each key has a list of two values because we will be animating two separate data points in this graph.

Task 2: Outlining Code

In this task, we'll create our skeleton outline of your code.

In your program code, define a function called create_circle_chart() and call it from your main function. This will be the top level function which produces your animated plot. It won't require any input parameters nor will it produce an output. It will simply visualize our first chart.

In this function, place the lines go.Figure(data = data, layout = layout, frames = frames) followed by plot(figure). The variables data, layout, and frames are the three necessary components for producing an animated plot. The data and layout are similar to the static plots we've been making before and the frames is an additional specification for the frames of your animation!

Let's create three separate functions for producing these three variables. You will need a create_data function, a create_layout function, and a create_frames function. The create_data function produces a plotly graphical object like we've had for static plots, so you will need to add input parameters for a list of x data, a list of y data as well as a marker color and a marker size. Each of these three functions should return the respective object they produce (data, layout, frames).

Inside your create_circle_chart() function, you will be calling the functions we just defined. So above the line where you create the go.Figure object, call your three new functions and assign them to appropriately named variables

Task 3: The Data Function

The create_data function will produce a plotly graphical object (Scatter, Bar, Scattergeo, Choropleth, etc) for each frame of the animation you are making. This function should have four input parameters, a list of float values for both the x and y axes, a marker color, and a marker size. It should also have a return statement, which returns the variable you will create in this function: presumably data. Check your code to be sure this is the case.

Creating the data object in this function is just simply a matter of creating a basic go.Scatter object. In this case, we'll have a column of x values, a column of y values, our plot mode will be just markers and we'll also specify a size and color for our markers using the input variables. Using the line below, replace the variables x and y with the names of the input variables you use in your function

data = [go.Scatter(x = x, y = y, mode = 'markers', marker = {'color': marker_color, 'size': marker_size})]

In your create_circle_chart() function, check that you call the create_data function appropriately. It should have four input parameters, the first list of x and y values (positions['x1'], and positions['y1']) an initial marker color and marker size.

Task 4: The frames function

The frames function will produce a list of frames which details each frame in your animation sequence. A frame is just a dictionary which includes a graphical object as one of its values. Therefor, we can call the create_data function from within. Check over your frames function, it should have a single return statement which returns the frames object you will create.

A frame is a snapshot of your plot at a given point in time. In this activity, we will be drawing two points in each frame. Each frame contains all the data points at that point in time. In successive frames, we update the x and y values, colors, and sizes of the points in order to animate them.

The code below provides an outline for producing the frames list, with a single demonstration of one frame of the animation. Remember each frame is a dictionary. Create the remaining four frames with the appropriate information. Also remember, the create_data function has four input parameters for the x values, the y values, the marker color and the marker size. Don't forget the commas at the end of each line!


frames = [{'data': create_data(positions['x1'], positions['y1'], "Blue", 10)},
           # Create the next four sequence of frames here
           #
           #
           #
           ]

If you want the data points in each frame to have different colors and sizes, you can use lists for the color and size keys. For example, if you give the value [100, 150] for the marker sizesize , then the first data point will have size 100 and the second data point will have size 150.

Task 5: The Layout Function

For animated visuals, the layout object gets slightly more complicated. Besides just a title, an x-axis label and a y-axis label, we'll also add a button object. This function does not need any input parameters, but check that your function includes an appropriate return statement.

Take a look at the following code to produce a layout object. It adds a lot more than what we're typically used to in a layout function, but we'll try our best to look at each piece here. For each item listed below, read the description carefully and replace the components in the code with the items required.

  • title: The name of the chart. Replace the included title with a title of your own
  • xaxis: Typically this is where we include our xaxis title, but for now we're just specifying the range of the x-axis and telling plotly to not autoscale the xaxis. Replace START and STOP with values that capture the ranges of the data.
  • yaxis: Like the xaxis, we won't let plotly autoscale the plot. Replace START and STOP with reasonable values.
  • updatemenus: This parameter tells plotly that we want to add interactive components to the plot
    • type: This specifies that the type of object we are adding is a buttons object
    • buttons: This provides the specific information for the buttons we are adding. This is actually a list of button objects, but for our purposes, we'll just add one.
    • method: This tells plotly what function to perform whenever the button is pressed, in this case it will be the animate function
    • label: This is the text printed inside the button and displayed to the user. Replace the text in the button with something meaningful
    • args: If we wanted to give specific arguments to the animate function, we would put them in this list. For now, we'll leave it blank

layout = go.Layout(title = "NAME OF CHART",
                   xaxis = {'range': [START, STOP], 'autorange': False},
                   yaxis = {'range': [START, STOP], 'autorange': False},
                   updatemenus = [{
                        'type': 'buttons',
                        'buttons': [{
                           'method': 'animate',
                           'label': 'NAME OF BUTTON',
                           'args': [None]}]
                    }])

Check over your layout function. You should have replaced any values that were in uppercase with values of your own choosing.

Task 6: Check over your code

We've now written a program to write an animated plot using plotly. It's fairly simple, but it's a great starting point. Check the following things before trying to run your program:

  • You call your create_circle_chart function from within main (and main itself is called as the very bottom line of your code)
  • Inside create_circle_chart you call the create_data, create_layout, and create_frames functions a single time. Each function call includes the correct amount parameters and this matches their respective function definitions above.
  • These functions are called before you create your figure object and plot
  • Your create data function should have four input parameters and at the very end of the function, a return statement
  • Your create layout function doesn't need any input parameters right now, but it should have a return statement at the end of your function
  • Your create_frames function builds a list of dictionaries. Inside each dictionary, you call create_data and the number of parameters here matches the number of function parameters, which should just be four.

Run your progam and check your results! Look carefully at an exceptions you get and debug any issues.

Feel free to play around with different colors, sizes, and positions for your data points.

Here is an example that you can use to check that your animated graph is working correctly.

Animating data from a CSV

Task 7: Duplicate your program

In the next part of this activity, you'll be plotting animations using data from a csv file. Specifically, you'll be plotting a graph of movie gross vs imdb scores for a director's movies in a given decade, using animations to cycle through years, displaying only a single year at a time. Your plot will look similar to this!

Let's create a new copy of our current program, and close the old one. We'll modify the copy to handle a pandas DataFrame. Remember to import pandas at the top of your new program. In your main function, use pandas to read this csv of movie data and create a data frame for it.

Task 8: Outline additional changes

We don't actually have to change much to animate this data instead. Rename create_circle_chart to create_movies_chart and add a single input parameter data_frame for the data it will make use of.

Creating the figure object and plotting it look the exact same as before, so you don't need to make additional changes here.

Make the following changes to the create_data, create_frames and create_layout function definitions and in the create_movies_chart where you call those functions.

  • Remove the marker color and marker size input parameters from the create_data function.
  • Add a data_frame input parameter to the create_frames function
  • Add a max_gross input parameter to the copied create_layout function.

Additionally, in the create_movies_chart function, calculate the variable max_gross and include it as an input parameter when you call the create_layout function. Max gross here should be the maximum gross across all columns and across all rows. Simply calling data_frame.max().max() will do the trick!

Also, in create_movies_chart change the line calling the create_data function so that x and the y input values refer to the '1970_imdb' and the '1970_gross' columns from your DataFrame.

Task 9: Updated data and frames functions

You should have removed the color and marker size parameters from the create_data function. So you should also then remove the marker parameter from the go.Scatter object. For now, we're just doing this to keep things simple, but you can add them back in if you want more style in your plot.

In the create_frames function you should modify the line calling the create_data function so that the number of parameters matches. So remove the color and marker size arguments. Also, replace the use of the positions dictionary with the correct columns from the DataFrame object. The x values should be the column of imdb ratings and the y values should be the column of film grosses pulled from the DataFrame corresponding to each year ('1970_imdb', '1970_gross' for the year 1970 for example)

Additionally, in order to interact nicely with the slider object, each frame dictionary should have a key-value pair for 'name'. This frame label will be associated with the label given to each step the slider object. In our case, we're using just the year as its value.


    frames = [
        {
          'data': CALL FUNCTION TO FIND DATA IN THIS YEAR,
          'name': YEAR GOES HERE
        },
        # Fill the other frames in here, or create a for loop
        #
        #
        ]

This is awfully repetitive. Can you create this list of frames objects using a for loop?

Task 10: The Layout and Slider

The layout for your animation is going to look a lot like the layout you used in your first animation but with added axis titles. It will also have a sliding bar at the bottom that lets you choose the decade.

In your layout function, add appropriate titles to the chart, xaxis and yaxis labels. Remember, for the xaxis and yaxis label dictionaries, they have a key for 'title' and a value which corresponds to the labels. Adjust the range of the y-axis by using the input parameter of your function max_gross as the maximum of the y-axis and give an appropriate range for the x-axis (0 - 10 for example).

Add a parameter and value to the Layout object sliders = [slider]. The sliders parameter tells plotly to add a slider to our animated figure.

To create the slider object, we'll add two more functions. A create_slider function with no input parameters. It should return a single slider object. Also define a create_slider_step function with a single input parameter year, that returns a single slider step. In the layout function, call the create_slider function and assign the result to a variable named slider

In your create_slider function, build a simple slider. This is a dictionary, with at the very least a single key-value of 'steps' set to a list of steps.

slider = {'steps' : steps}

Just above this line, we'll need to actually create the list of steps. Using a list expression or a for loop, iterate through the years from 1970 up to and including 2010 by 10s and call create_slider_step to create a list of slider steps.

In your function create_slider_step verify that it has a single input parameter year, and returns a slider step. The slider step is a dictionary, which is very similar to the buttons object we defined before, which makes sense since it is an interactive component for our charts.

There is a lot of information contained within. Look carefully at each element in this list and replace the necessary components.

  • label: The text label displayed for each frame. Replace the value with the year variable.
  • method: Just like the button, we want to tell plotly to animate the transition to this frame
  • args: This is a list of arguments to the animate function that tells plotly how to perform the animation
    • YEAR GOES HERE, This tells the function which frame to transition to, replace this with the actual year variable
    • mode: This tells the animate function how to transition as the slider is being dragged along. In this case, the transitions should be immediate.
    • transition: This also elimates any transition between frames and makes the slider act more responsively.

slider_step = {
      'label': YEAR GOES HERE,
      'method': 'animate',
      'args': [
        [YEAR GOES HERE],
        {'frame': {'duration': 200, 'redraw': False},
         'mode': 'immediate',
       'transition': {'duration': 0}}],
   }

Task 11: Check and run your code

Check over your code and look for any glaring bugs. Once you are satisfied with the code, run your program and debug any further issues you may have. Your plot should look similar to the one below, but without colors or hover text.

If you have more time

Take some time to add nice things to your animation. Take a look at the following list and try any that seem tempting. Completing all of these should give you a chart that looks more similar to the chart above.

Add hover text for each director

This will need to be handled in your create_data function. Do you remember the parameter we need to add to the go.Scatter object? What additional information does this function need to provide that?

Plot each director with a different color

This can also happen in your create_data function. The data object here is typically a single element list, but if we create a list of many elements, each one will be displayed with a different color. In this case we want a list of Scatter objects: one for each director.

Adjust the spacing and formatting around the slider

The slider object we added was just placed automatically by plotly, and unfortunately it interferes with the x-axis label. Also the current value that is displayed is too small

In your create_slider function, you can add more key value pairs to the slider dictionary to provide this formatting.

  • 'x': Specifies what the x position of the slider should be. The value is a fraction, so 0.0 is all the way to the left, and 1.0 would be all the way to the right. This should be accompanied by an 'xanchor' key
  • 'xanchor': When providing an x location, this key specifies what is considered the positioning element of the slider. 'left' is a suitable value, but you can also use 'center' or 'right'. So an 'xanchor' of 'left' and an 'x' value of 0.0, would place the slider all the way to the left.
  • 'y': Specifies what the y position of the slider should be. The value is a fraction, so 0.0 is all the way to the bottom, and 1.0 would be at the top, but these bounds can extend beyond. This should be accompanied by an 'yanchor' key
  • 'yanchor': When providing an y location, this key specifies what is considered the positioning element of the slider. 'top' is a suitable value, but you can also use 'middle' or 'bottom'.
  • 'len': The length of the slider. This is a fractional value, where 1 is the full width of the plot.
  • 'pad': This specifies the padding around the slider object. It's a dictionary object, so a value of {'b': 10, 't': 50} implies that the padding at the bottom would be 10 pixels and at the top would be 50 pixels.
  • 'currentvalue': This provides formatting for displaying the current value the slider has selected. It can be helpful to make this pop out a bit more. For example, by providing a dictionary like {'font': {'size': 20}, 'xanchor': 'right'} you can increase the size of the font and move the current value to the right.

Plotly examples

Look through the animation tutorials on Plotly: https://plot.ly/python/animations/. Remember that you'll need to use offline plotting and plot, not iplot.


Once you're done, please check off your lab with a TA or share your file with cs0030handin@gmail.com by midnight, 4/25.