Looking for a solution to properly annotate a subplot with an ordered pair of cartesian coordinates.
My figure is a bar graph of total product quantities with a line graph of the average price for the given products. For additional reference, please see the figure at the end of this article: https://medium.com/swlh/product-sales-analysis-using-python-863b29026957
Please note, I have two vertical axes where:
- y1 = total quantity of a given product
- y2 = average price of a given product
- y1 & y2 share an x-axis of product categories
Rather than plotting labels "(x, y)", my goal is to plot labels for (y1, y2), i.e. "(qty, price)".
The current error that I am running into is that the list elements in my variable, label, are not recognized as "subscriptable objects". I am under the impression that the solution is to convert each element of my list into a string, but I am not positive.
df =
| Products | Quantity | Price |
|---|---|---|
| Product1 | 10 | 100.00 |
| Product2 | 15 | 200.00 |
| Product3 | 20 | 150.00 |
| Product2 | 30 | 200.00 |
| Product3 | 50 | 150.00 |
Attempt
quantity = df.groupby("Products")["Quantity"].sum()
price = df.groupby("Products")["Price"].mean()
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
ax1.bar(Products, quantity, color='.8', alpha =.8)
ax2.plot(Products, price, 'bo-')
ax1.set_xlabel('', fontweight='bold')
ax1.set_ylabel('Quantity', color = 'k', fontweight='bold')
ax2.set_ylabel('Price $', color = 'b', fontweight='bold')
ax1.set_xticklabels(Products, rotation=45, size = 8)
y1 = [i for i in quantity]
y2 = [j for j in price]
label = []
for x, y in zip(y1,y2):
label.append(f"({x:.2f},{y:.2f})")
for i, label in enumerate(labels):
plt.annotate(label, xy=(x[i], y[i]), xytext=(5, 5),
textcoords='offset points', ha='left', va='bottom')
plt.show()
Trouble Area
#can't find a method to convert my list elements from float to string values *inline* with label.append()
label = []
for x, y in zip(y1,y2):
label.append(f"({x:.2f},{y:.2f})")
I feel like I am looking for a solution similar to either:
There are a few misunderstandings in the code:
ax1andax2, it is recommended to use matplotlib's object-oriented interface everywhere.plt.annotate(...)will plot on the "current ax", whileax1.annotate(...)will plot onax1.ax1, the x-coordinate can be given as a string (the name of the product), and the y-coordinate as the numeric quantity.enumerate(...)and indexing. Loops are clearer using zip to directly get the list elements.ax.tick_params(...)will leave the existing labels untouched.ax1.margins(y=...)can make more free space for the labels.