3

I would like to add image annotations to a boxplot, akin to what they did with the bar chart in this post: How can I add images to bars in axes (matplotlib)

My dataframe looks like this:

import pandas as pd
import numpy as np

names = ['PersonA', 'PersonB', 'PersonC', 'PersonD','PersonE','PersonF']
regions = ['NorthEast','NorthWest','SouthEast','SouthWest']
dates = pd.date_range(start = '2021-05-28', end = '2021-08-23', freq = 'D')

df = pd.DataFrame({'runtime': np.repeat(dates, len(names))})
df['name'] = len(dates)*names
df['A'] = 40 + 20*np.random.random(len(df))
df['B'] = .1 * np.random.random(len(df))
df['C'] = 1 +.5 * np.random.random(len(df))
df['region'] = np.resize(regions,len(df))

I tried to use the AnnotationBbox method which worked great for my time-series, but I'm not entirely sure if it can be applied here.

import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
from matplotlib.cbook import get_sample_data

fig, ax = plt.subplots(
df.boxplot(column='A', by=['name'],ax=ax,showmeans=True, fontsize=8, grid=False)

for name in names:
  rslt_df = df[df['name']==name] 
  val = rslt_df['A'].values[0]
  xy = (0, val)

  fn = get_sample_data(f"{name}.png", asfileobj=False)
  arr_img = plt.imread(fn, format='png')
  imagebox = OffsetImage(arr_img, zoom=0.125)
  imagebox.image.axes = ax

  ab = AnnotationBbox(imagebox, xy,xybox=(15.,0),xycoords='data',boxcoords="offset points",pad=0,frameon=False)
  ax.add_artist(ab)

PersonA.png PersonB.png PersonC.png

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
p3hndrx
  • 103
  • 9

2 Answers2

2
  • The code in the OP if very similar to Add image annotations to bar plots axis tick labels, but needs to be modified because boxplots are slightly different the barplots.
  • The main issue was xy didn't have the correct values.
    • The xy and xybox parameters can be adjusted to place the images anywhere.
  • By default, boxplot positions the ticks at range(1, n+1), as explained in this answer
    • Reset the tick positions with a 0 index: positions=range(len(names))
  • df was created with names = ['PersonA', 'PersonB', 'PersonC'] since only 3 images were provided.
ax = df.boxplot(column='A', by=['name'], showmeans=True, fontsize=8, grid=False, positions=range(len(names)))
ax.set(xlabel=None, title=None)

# move the xtick labels
ax.set_xticks(range(len(names)))
ax.set_xticklabels(countries)
ax.tick_params(axis='x', which='major', pad=30)

# use the ytick values to locate the image
y = ax.get_yticks()[1]

for i, (name, data) in enumerate(df.groupby('name')):

    xy = (i, y)

    fn = f"data/so_data/2021-08-28/{name}.png"  # path to file
    arr_img = plt.imread(fn, format='png')
    imagebox = OffsetImage(arr_img, zoom=0.125)
    imagebox.image.axes = ax

    ab = AnnotationBbox(imagebox, xy, xybox=(0, -30), xycoords='data', boxcoords="offset points", pad=0, frameon=False)
    ax.add_artist(ab)

enter image description here

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
  • 1
    "By default, boxplot positions the ticks at range(1, n+1), so reset it with a 0 index positions=range(len(names))" WOW. Yes, this helps SO MUCH. – p3hndrx Aug 28 '21 at 22:49
  • @p3hndrx I didn’t know about the parameter until I made this [answer](https://stackoverflow.com/a/68958662/7758804) the other day – Trenton McKinney Aug 29 '21 at 00:27
0

I noticed that the y-ticks don't always position themselves in a friendly manner, so I set a static Y-value (the x-axis). Creating a transform xycoords, allows placement directly below the x-axis, no matter the y-tick scale.

# BOX GRAPH PLOT
fig, ax = plt.subplots(facecolor='darkslategrey')
plt.style.use('dark_background')

ax = df.boxplot(column=str(c), by=['name'],ax=ax,showmeans=True, fontsize=8,grid=False,positions=range(len(top)))
ax.set(xlabel=None, title=None)


# move the xtick labels
ax.set_xticks(range(len(top)))
ax.tick_params(axis='x', which='major', pad=20)

# use the ytick values to locate the image
y = ax.get_xticks()[0]


for i, (name, data) in enumerate(df.groupby('name')):
    xy = (i, y)

    fn = f"{imgsrc}/{name}.png"  # path to file
    arr_img = plt.imread(fn, format='png')
    imagebox = OffsetImage(arr_img, zoom=0.125)
    imagebox.image.axes = ax

    trans = ax.get_xaxis_transform()
    ab = AnnotationBbox(imagebox, xy, xybox=(0, -15), xycoords=trans,boxcoords="offset points", pad=0, frameon=False)
    
    ax.add_artist(ab)

    plt.show()
p3hndrx
  • 103
  • 9