Featured resource
Tech Upskilling Playbook 2025
Tech Upskilling Playbook

Build future-ready tech teams and hit key business milestones with seven proven plays from industry leaders.

Learn more
  • Labs icon Lab
  • Data
Labs

Build a Donut Chart in Python to Visualize Employee Skill Distribution

In this Code Lab, you will build polished donut charts in Python to visualize the distribution of employee skills across a tech company. You will start by creating a basic donut chart using Matplotlib. Next, you will customize it with a color palette, labels, legends, explode effects, and rotation. You will then build an interactive donut chart using Plotly Express, adding hover details and a slice pull to emphasize the largest category. Finally, you will annotate your chart with the total employee count and use pandas to identify the most and least common skills.

Labs

Path Info

Level
Clock icon Intermediate
Duration
Clock icon 49m
Last updated
Clock icon Aug 29, 2025

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Table of Contents

  1. Challenge

    Step 1: Build a Basic Donut Chart with Matplotlib

    Step 1: Build a Basic Donut Chart with Matplotlib

    In this step, you'll construct a foundational donut chart using Matplotlib. You'll begin by rendering a basic pie chart and then apply styling to transform it into a donut. This includes setting the wedge width and ensuring the chart maintains a consistent aspect ratio.

    This step establishes the structural core of the chart. All enhancements in future steps, such as colors, interactivity, and annotations, will build upon this initial configuration.


    What You’ll Learn in This Step

    • Use matplotlib.pyplot to generate a pie chart with categorical labels
    • Modify pie chart geometry using the wedgeprops parameter
    • Normalize aspect ratio using axis("equal") for consistent layout

    Open the Notebook

    From the workspace panel, open the notebook file: 1-step-one.ipynb.

    info> Important: Save your notebook (Ctrl/Cmd + S) before clicking Validate. Validation inspects the most recent saved checkpoint.

    Understanding Donut Geometry and ax.pie()

    The foundation of any Matplotlib chart begins with creating a Figure and Axes object using plt.subplots(). This object-oriented approach offers fine-grained control over how plots are rendered and customized.

    To draw a pie chart, Matplotlib provides the Axes.pie() method. It takes a sequence of values (such as category counts) and renders them as proportional wedges in a circular plot. For each wedge, you can supply:

    • labels: a list of category labels to annotate each segment
    • wedgeprops: a dictionary that defines how the wedges are styled

    To create a donut chart, set wedgeprops={"width": 0.3}. This parameter reduces the width of each wedge, creating a hollow center. Unlike pie charts, this space can later be used to display aggregate metrics or annotations.

    Lastly, the chart won't display automatically unless you call plt.show()—a required step when working in scripts or Jupyter notebooks that don’t have auto-rendering enabled.

    This task focuses purely on initializing and rendering the visual structure of the donut.

    Controlling Aspect Ratio for Accurate Donut Charts

    Matplotlib allows full control over plot layout through its axis configuration system. When rendering circular visualizations—such as pie or donut charts—maintaining a 1:1 aspect ratio between the x and y axes is critical for preserving geometric fidelity.

    By default, Matplotlib may stretch or compress a plot to fit the cell’s available space, especially in notebook environments. This can cause a donut chart to appear elliptical, even if the underlying data is correct.

    To enforce a true circle, you must explicitly set the aspect ratio to "equal" using:

    ax.axis("equal")
    

    This instructs Matplotlib to allocate equal units along both axes, preventing distortion. It’s a common best practice in all circular data visualizations—especially when visual proportions must be preserved for accurate interpretation.

  2. Challenge

    Step 2: Enhance the Donut Chart with Colors and Legends

    Step 2: Enhance the Donut Chart with Colors and Legends

    In this step, you’ll improve the readability and clarity of your donut chart by applying stylistic enhancements. Using Matplotlib's customization features, you’ll define a color palette, add percentage labels, and include a legend and title for better context.

    You’ll also use an explode effect to offset the dominant skill slice and apply rotation to control how the chart is oriented.


    What You’ll Learn in This Step

    • Apply a categorical color palette to ax.pie()
    • Display percentage labels on each wedge
    • Add a chart title and build a custom legend
    • Emphasize the largest slice with an explode effect
    • Rotate the chart using the startangle parameter

    Open the Notebook

    From the workspace panel, open the notebook file: 2-step-two.ipynb.

    info> Important: Save your notebook (Ctrl/Cmd + S) before clicking Validate. Validation inspects the most recent saved checkpoint. ### Applying Color Palettes with Matplotlib’s get_cmap()

    To control the visual styling of your donut chart, Matplotlib allows you to assign a color to each wedge using the colors argument in ax.pie().

    Instead of hardcoding RGB or hex values, you can use built-in colormap objects. These are retrieved with plt.cm.get_cmap(), which returns a color map you can sample programmatically.

    For categorical data like skills, a qualitative palette such as "Pastel1" or "Set3" works well. To assign colors, call the colormap with the number of categories:

    colors = plt.cm.get_cmap("Pastel1")(range(len(employee_data)))
    

    This line generates a list of RGBA color values based on the number of skill categories in your dataset.

    You then pass this list to the colors parameter in your ax.pie() call:

    ax.pie(..., colors=colors)
    

    Other Available Colormaps

    You can experiment with other pastel or qualitative palettes to adjust the look of your chart. Here are a few compatible options:

    "Pastel1" (default in this step)

    "Pastel2"

    "Set1"

    "Set2"

    "Set3"

    "Accent"

    "Paired"

    "tab10"

    "tab20"
    To learn more, visit: https://matplotlib.org/stable/users/explain/colors/colormaps.html

    ### Annotating Donut Charts with Percentage Labels

    Matplotlib’s ax.pie() function supports displaying percentage labels on each wedge using the autopct argument. This argument accepts a format string or a function that controls how percentages are displayed.

    A common approach is to pass a format string like:

    autopct="%.1f%%"
    

    This renders each wedge with its percentage value to one decimal place (e.g., 22.5%). Internally, Matplotlib calculates these values based on the fraction of the total sum each wedge represents.

    If you need more control over formatting or filtering, you can supply a custom function to autopct instead. However, for most categorical data visualization, a simple format string is sufficient.

    The percentage labels are automatically placed at the center of each wedge unless offset by labeldistance or pctdistance.

    Using autopct makes the chart self-contained and interpretable without requiring the user to cross-reference a legend or raw data table. ### Adding Titles and Legends for Clarity

    A visualization is only effective if the audience can interpret it quickly. In Matplotlib, you can add essential context with titles and legends.

    Titles
    Use ax.set_title("Your Title") to display a descriptive heading above the chart. Titles should summarize what the viewer is seeing in concise terms, such as "Employee Skill Distribution".

    Legends
    Legends in Matplotlib are built using ax.legend(). When ax.pie() is called with labels=, those labels can be reused in the legend automatically. You can also customize the legend’s position using the loc parameter (e.g., "center left", "upper right", etc.) and anchor its placement with bbox_to_anchor.

    Example:

       ax.legend(loc="center left", bbox_to_anchor=(1, 0, 0.5, 1))
    

    This places the legend outside the chart area, ensuring it doesn’t overlap with the donut itself.

    • Combined Effect Together, titles and legends provide both immediate chart context and a key for interpreting wedges, reducing cognitive load on the viewer.

    This task focuses on integrating both elements so your donut chart communicates data effectively at a glance. ### Highlighting the Largest Wedge Using Explode Logic

    Matplotlib’s explode parameter allows individual wedges in a pie or donut chart to be offset from the center, visually drawing attention to a particular category. This is especially useful when you want to emphasize the largest segment in a dataset.

    To determine which wedge represents the dominant category, use the idxmax() method on the relevant column of your DataFrame. This function returns the index of the row containing the highest value in a Series. For example, if "count" is the column representing category totals, the syntax would be:

    idx = dataframe["count"].idxmax()
    

    This index can then be used to build a list of floats, one for each row in the dataset, where only the largest category receives an offset value (commonly 0.1), and all others remain at 0. This list must be the same length as the number of wedges you plan to render. Use a list comprehension like the following:

    explode = [0.1 if i == idx else 0 for i in range(len(dataframe))]
    

    The explode list should be passed into the ax.pie() method to apply the offset:

    ax.pie(
        dataframe["count"],
        labels=dataframe["skill"],
        wedgeprops={"width": 0.3},
        autopct="%.1f%%",
        explode=explode
    )
    

    The explode values control how far each wedge is pushed outward from the chart’s center. This feature is a valuable design tool for drawing attention to specific insights in your data, particularly when identifying top-performing categories. ### Improving Readability with Padding and Font Size

    When working with donut charts that contain labels and percentage values, small adjustments in padding and text formatting can significantly enhance readability—especially when dealing with long labels or dense chart segments.

    Matplotlib allows for these refinements through several parameters inside the ax.pie() function:

    • labeldistance controls the radial distance of category labels from the center. Increasing this value (e.g., 1.15) pushes the labels further outward, reducing overlap with the chart.
    • pctdistance adjusts how close the percentage text sits to the center. Lower values (e.g., 0.65) move the percentages deeper inside the donut ring for balance and clarity.

    Both values are expressed as floats relative to the chart's radius.

    To further improve visual clarity, you can modify text style directly. After rendering the pie chart, ax.pie() returns three elements: wedges, text labels, and percentage labels. You can loop through the percentage labels (autotexts) and apply custom formatting like font size:

    for autotext in autotexts:
        autotext.set_fontsize(10)
    

    This approach is particularly helpful when default font sizes appear too small or inconsistent across different resolutions. These small refinements ensure that your donut chart is not only functionally correct but also visually professional.

  3. Challenge

    Step 3: Create an Interactive Donut Chart with Plotly Express

    Step 3: Create an Interactive Donut Chart with Plotly Express

    In this step, you'll transition from static Matplotlib charts to dynamic Plotly visualizations. Plotly Express makes it easy to convert your donut chart into an interactive experience, complete with hover details, layout customization, and animated transitions.

    This step teaches how to recreate the same visual insights from previous steps while enhancing user interaction through a modern plotting library.


    What You’ll Learn in This Step

    • Create a donut chart using plotly.express.pie() with interactive features
    • Customize the layout, hover behavior, and styling using update_layout
    • Use the pull parameter to highlight slices dynamically

    Open the Notebook

    From the workspace panel, open the notebook file: 3-step-three.ipynb.

    info> Important: Save your notebook (Ctrl/Cmd + S) before clicking Validate. Validation inspects the most recent saved checkpoint. ### Generate a Donut Chart Using Plotly Express

    Plotly Express is a high-level API that simplifies the creation of interactive visualizations. To generate a donut chart, you use the px.pie() function. Although named pie, this function supports donut charts by setting the hole parameter to a value between 0 and 1.

    The two most important arguments for px.pie() are:

    • names: a sequence of categorical values used to label each wedge
    • values: a sequence of numeric values that determine the size of each wedge

    Both names and values can be passed as column references from a Pandas DataFrame, or as separate sequences. Typically, names is a column of type object or string, while values must be numeric—either integers or floats. These inputs allow Plotly to compute proportions and assign labels automatically.

    To create a donut chart, include the hole argument:

    import plotly.express as px
    
    fig = px.pie(
        names=your_dataframe["label_column"],
        values=your_dataframe["value_column"],
        hole=0.4
    )
    

    The returned object fig is a Plotly Figure that can be further customized. To display the chart, use:

    fig.show()
    

    This produces an interactive donut chart with tooltips, hover events, zoom, and export features—all rendered in the notebook. ### Add Hover Data and Color Customization in Plotly Express

    Plotly Express supports custom tooltips and color styling to enrich interactive charts. When using px.pie(), these features are controlled by the hover_data and color_discrete_sequence parameters.

    The hover_data argument accepts a list of column names to display on hover. These should reference valid columns from the DataFrame and typically contain numerical values that provide extra insight, such as raw counts or pre-calculated percentages. If a column is not included in names or values, adding it to hover_data ensures it still appears on hover.

    To match the look of earlier Matplotlib charts, you can apply a consistent pastel palette using the color_discrete_sequence argument. This parameter expects a list of color values as hex strings. A common technique is to convert colors from Matplotlib’s "Pastel1" colormap using matplotlib.colors.to_hex():

    import matplotlib.colors as mcolors
    cmap = plt.cm.get_cmap("Pastel1")
    colors = [mcolors.to_hex(cmap(i)) for i in range(len(employee_data))]
    

    This list can then be passed directly into px.pie():

    px.pie(..., color_discrete_sequence=colors)
    

    Hover data and color consistency enhance readability and ensure a smooth visual transition between static and interactive charts.

    Other Available Colormaps

    You can experiment with other pastel or qualitative palettes to adjust the look of your chart. Here are a few compatible options:

    • "Pastel1" (default in this step)
    • "Pastel2"
    • "Set1"
    • "Set2"
    • "Set3"
    • "Accent"
    • "Paired"
    • "tab10"
    • "tab20"

    To preview them, visit: https://matplotlib.org/stable/users/explain/colors/colormaps.html

    ### Customize Layout and Titles in Plotly Figures

    Plotly figures can be styled using the update_layout() method, which modifies global chart properties like title, legend, margins, and background. This method operates on the Figure object returned by functions such as px.pie().

    To set the main chart title, use title_text. This is a string that appears above the chart and helps users understand the context of the data.

    To customize the legend, you can use legend_title, which sets a label above the category list on the right-hand side. This is especially useful when the names field in your chart is derived from a generic column like "skill".

    Finally, to control whitespace around the plot, pass a dictionary to the margin parameter. For example:

    fig.update_layout(
        title_text="Employee Skill Distribution",
        legend_title="Skill",
        margin=dict(l=0, r=0, t=50, b=0)
    )
    
    • l, r, t, and b stand for left, right, top, and bottom margins respectively.
    • These values are in pixels and control how tightly the chart content is packed.

    This method is essential for refining a chart’s appearance in both notebooks and exported dashboards.

    Emphasize the Largest Slice in an Interactive Plotly Donut

    Plotly Express doesn't support the pull argument directly inside px.pie(). To offset or emphasize a specific wedge, you use the update_traces() method on the returned Figure object.

    This method lets you set the pull value per slice after the chart has been created. pull takes a list of floats, where each value represents the radial distance to offset that slice. For example, a value of 0.1 pulls a slice outward slightly, while 0 keeps it in place.

    To programmatically offset the largest slice:

    • First, use .idxmax() to find the index of the row with the highest count:
      	idx = employee_data["count"].idxmax()
      

    This returns an integer representing the row position of the dominant skill.

    • Then, build a list of offsets using a list comprehension:

      	pull_values = [0.1 if i == idx else 0 for i in range(len(employee_data))]
      

    This list ensures only the largest category is emphasized.

    Finally, apply the pull setting by calling:

    fig.update_traces(pull=pull_values)
    

    This approach creates a subtle highlight that draws the viewer's attention without distorting the chart structure.

  4. Challenge

    Step 4: Annotate and Analyze the Chart

    Step 4: Annotate and Analyze the Chart

    In this step, you'll enhance your visualization by embedding meaningful annotations and summarizing key insights from the dataset. Annotations like totals inside the donut and lists of top/bottom skills help viewers quickly understand the chart's context and scale.

    By combining pandas analysis with chart annotations, you'll make your visual output both informative and interactive.


    What You’ll Learn in This Step

    • Insert dynamic annotations into a Matplotlib chart using ax.text()
    • Use pandas methods like sum(), nlargest(), and nsmallest() for insight extraction
    • Combine data summary with visualization for full analytical impact

    Open the Notebook

    From the workspace panel, open the notebook file: 4-step-four.ipynb.

    info> Important: Save your notebook (Ctrl/Cmd + S) before clicking Validate. Validation inspects the most recent saved checkpoint.

    Centering Text in Donut Charts with ax.text()

    To display a total value inside the center of a donut chart, you'll combine Matplotlib’s ax.text() method with a computed total from your dataset.

    You can calculate the total number of employees by summing the values in the count column using:

    total = employee_data["count"].sum()
    

    This returns a single number representing the total across all categories.

    To place this number in the center of your donut, use:

    ax.text(0, 0, f"Total\n{total}", ha="center", va="center")
    

    The coordinates (0, 0) correspond to the center of the chart. The f"Total\n{total}" string places the label and number on separate lines. The ha (horizontal alignment) and va (vertical alignment) parameters ensure the text is centered precisely inside the donut.

    This technique improves chart readability and offers a quick summary insight directly within the visual. ### Extracting Top and Bottom Records Using nlargest() and nsmallest()

    Pandas provides built-in methods to quickly identify the largest or smallest records in a DataFrame—helpful for surfacing insights like most or least common values.

    • nlargest(n, column) returns the top n rows sorted by the specified column in descending order.
    • nsmallest(n, column) returns the bottom n rows sorted by the specified column in ascending order.

    Both methods preserve the original column order and return a new DataFrame. The first argument specifies how many records to return, while the second identifies which column to sort by.

    In this task, you’ll use:

    employee_data.nlargest(3, "count")
    employee_data.nsmallest(3, "count")
    

    These two calls return the top and bottom three skills based on frequency. You can pass the results directly to print() or assign them to variables if further analysis is needed.

    This technique is a quick and powerful way to highlight dominant or rare categories in your data.

What's a lab?

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Provided environment for hands-on practice

We will provide the credentials and environment necessary for you to practice right within your browser.

Guided walkthrough

Follow along with the author’s guided walkthrough and build something new in your provided environment!

Did you know?

On average, you retain 75% more of your learning if you get time for practice.