1

I posted a question previously but my wording got it flagged as a duplicate, so I'll be super specific on this one.

This is 13 rows of data, but my df has about 160:

Animal  Score
0   Dog 1
1   Pig 2
2   Chicken 3
3   Cat 4
4   Fox 5
5   Whale 6
7   Beetle 7
8   Ox 8
9   Monkey 9
10  Cow 10
11  Duck 11
12  Hen 12
13  Crow 13

I'm trying to annotate a graph with the values, and there is one line of code that's buggin' out on me and I cannot figure out why - ax.set_xticklabels(x_labels)

I think it's because of the concat that's forcing 11 rows instead of the full 160 odd, but here's the code:

#Add custom entries here
custom_df = dfAnimalRankings.loc[dfAnimalRankings['Animals'].isin(['Beetle'])]

# Create new df to show top 5, bottom 5, and Beetle
new_df = pd.concat([dfAnimalRankings[:5], dfAnimalRankings[-5:], custom_df])
new_df.sort_values(by=['Score'], inplace= True)
new_df.reset_index(drop=True, inplace= True)

# Plot the figure.
plt.figure(figsize=(12, 8))
ax = new_df.plot(kind='bar')
ax.set_title('Animal Rankings')
ax.set_xlabel('Animals')
ax.set_ylabel('Score')
ax.set_xticklabels(x_labels)

rects = ax.patches

def add_value_labels(ax, spacing=5):
    """Add labels to the end of each bar in a bar chart.

    Arguments:
        ax (matplotlib.axes.Axes): The matplotlib object containing the axes
            of the plot to annotate.
        spacing (int): The distance between the labels and the bars.
    """

    # For each bar: Place a label
    for rect in ax.patches:
        # Get X and Y placement of label from rect.
        y_value = rect.get_height()
        x_value = rect.get_x() + rect.get_width() / 2

        # Number of points between bar and label. Change to your liking.
        space = spacing
        # Vertical alignment for positive values
        va = 'bottom'

        # If value of bar is negative: Place label below bar
        if y_value < 0:
            # Invert space to place label below
            space *= -1
            # Vertically align label at top
            va = 'top'

        # Use Y value as label and format number with one decimal place
        label = "{:.1f}".format(y_value)

        # Create annotation
        ax.annotate(
            label,                      # Use `label` as label
            (x_value, y_value),         # Place label at end of the bar
            xytext=(0, space),          # Vertically shift label by `space`
            textcoords="offset points", # Interpret `xytext` as offset in points
            ha='center',                # Horizontally center label
            va=va)                      # Vertically align label differently for
                                        # positive and negative values.


# Call the function above. All the magic happens there.
add_value_labels(ax)

plt.savefig("image.png")

If I run the above code, I get the following error:

The number of FixedLocator locations (11), usually from a call to set_ticks, does not match the number of ticklabels (166).

But if I run this code:

#Add custom entries here
custom_df = dfAnimalRankings.loc[dfAnimalRankings['Animals'].isin(['Beetle'])]

# Create new df to show top 5, bottom 5, and Beetle
new_df = pd.concat([dfAnimalRankings[:5], dfAnimalRankings[-5:], custom_df])
new_df.sort_values(by=['Score'], inplace= True)
new_df.reset_index(drop=True, inplace= True)

# Plot the figure.
plt.figure(figsize=(12, 8))
ax = new_df.plot(kind='bar')
ax.set_title('Animal Rankings')
ax.set_xlabel('Animals')
ax.set_ylabel('Score')
# ax.set_xticklabels(x_labels)

rects = ax.patches

def add_value_labels(ax, spacing=5):
    """Add labels to the end of each bar in a bar chart.

    Arguments:
        ax (matplotlib.axes.Axes): The matplotlib object containing the axes
            of the plot to annotate.
        spacing (int): The distance between the labels and the bars.
    """

    # For each bar: Place a label
    for rect in ax.patches:
        # Get X and Y placement of label from rect.
        y_value = rect.get_height()
        x_value = rect.get_x() + rect.get_width() / 2

        # Number of points between bar and label. Change to your liking.
        space = spacing
        # Vertical alignment for positive values
        va = 'bottom'

        # If value of bar is negative: Place label below bar
        if y_value < 0:
            # Invert space to place label below
            space *= -1
            # Vertically align label at top
            va = 'top'

        # Use Y value as label and format number with one decimal place
        label = "{:.1f}".format(y_value)

        # Create annotation
        ax.annotate(
            label,                      # Use `label` as label
            (x_value, y_value),         # Place label at end of the bar
            xytext=(0, space),          # Vertically shift label by `space`
            textcoords="offset points", # Interpret `xytext` as offset in points
            ha='center',                # Horizontally center label
            va=va)                      # Vertically align label differently for
                                        # positive and negative values.


# Call the function above. All the magic happens there.
add_value_labels(ax)

plt.savefig("image.png")

I get this (I'm aware the numbers don't match, but it's an output from my actual dataset not the sample above):

enter image description here

Why does that one line of code break the annotation? And how can I get the data labels, which are animal names and not the numbers, added to the chart?

Cheers

The Singularity
  • 2,428
  • 3
  • 19
  • 48
Aemonar
  • 63
  • 1
  • 9
  • I don't see `x_labels` defined anywhere – The Singularity Sep 19 '21 at 12:15
  • good point, but adding in `x_labels = dfAnimalRankings['Animals'].tolist() # Plot the figure. plt.figure(figsize=(12, 8)) ax = new_df.plot(kind='bar') ax.set_title('Animal Rankings') ax.set_xlabel('Animals') ax.set_ylabel('Score') ax.set_xticklabels(x_labels)` still gives me `The number of FixedLocator locations (11), usually from a call to set_ticks, does not match the number of ticklabels (166).` when run so I can't confirm if it worked or not? – Aemonar Sep 19 '21 at 12:22
  • Why are you doing this? Make sure you're updated to `matplotlib v3.4.2`, remove your entire function and use `ax.bar_label(ax.containers[0], label_type='edge')` as per this [answer](https://stackoverflow.com/a/67561982/7758804) – Trenton McKinney Sep 19 '21 at 13:07
  • I removed everything from `rects =ax.patches` down to `add_value_labels(ax)` and replaced it with `ax.bar_label(ax.containers[0], label_type='edge')` and got this error message `'AxesSubplot' object has no attribute 'bar_label'` Edit: I'm running 3.3.4, which is why. – Aemonar Sep 19 '21 at 15:24
  • 1
    Updated and that worked amazingly - thank you so much for the super simple tip! – Aemonar Sep 19 '21 at 15:58

1 Answers1

1

The reason why you're getting an error is clear, your plot has only 11 bars but you're passing 166 labels to them. My guess is you've mixed up your dfAnimalRankings and new_df as a result you've got mismatched labels.

The workaround here would be to set x_labels to

x_labels = new_df['Animals'].tolist()

That way you'll get exactly 11 labels for 11 bars.

The Singularity
  • 2,428
  • 3
  • 19
  • 48