Code Hack - How to render images inside a pandas DataFrame ? Super cool trick with an advanced bonus :)
Have you ever wondered if there was an easy way too render images inside a pandas DataFrame ?
How cool would it be to simply write down something like render_images(df) and that's it, that would do the trick for you ?
Today I'll show you a super cool trick to exactly do that ! Even better, I'll show you an "advanced technique" so that you could even do something like df.render_images(). In other words, you'll include the rendering function directly in the pd.DataFrame object :)

First things first, the normal way to display images in notebooks is to use libraries like matplotlib, PIL, OpenCV etc...
Let's go ahead with the PIL library to show a quick example.
from PIL import Image
path_to_img = 'images/standard_scaler_logo_black.jpg'
# Create the image instance
img = Image.open(path_to_img)
# Here you can either use display(img) or img.show()
display(img)

Now you can normally see a beautiful logo :)
That being said, you can't really use these kind of libraries along with dataframes, they simply don't work together.
How to do then ? Well there's a cool module that is able to render html stuff that is IPython.core.display.HTML
basically you provide the html code, it will render stuff, simple, efficient.
Bellow a simple html code to display an image and to constraint its width to 100px.
<img src="/assets/media-export-203664368-from-0-to-2031/2023/02/standard_scaler_logo_black.jpg" width="100">
src can be the local path to the image or its url.
Let's see what IPython.core.display.HTML can do for us
from IPython.core.display import HTML
HTML('<img src="/assets/media-export-203664368-from-0-to-2031/2023/02/standard_scaler_logo_black.jpg" width="100">')

Having worked on some websites, I personally find it very satisfying to display an image with html code, that stuff is very smooth.
The best part of this thing is yet to come :) . For now, let's write a simple fonction that automatically does the html formatting.
def image_formatter_html(path_or_url):
"""html formatter for images. It takes as input an image path or
a image url."""
html_code = f'<img src="{path_or_url}" width="100">'
return html_code
# Now we can do all the stuff with a one-liner.
HTML(image_formatter_html(path_to_img))

Let's build a simple 3 columns DataFrame. One of the columns is logo_path and it contains the paths to the images
import pandas as pd
data_as_dict ={
"image_name":[
"logo_black",
"logo_blue",
"logo_double",
"logo_purple"
],
"image_description":[
"standard logo",
"background blue",
"double effect",
"background purple"
],
"logo_path":[
"images/standard_scaler_logo_black.jpg",
"images/standard_scaler_logo_blue.jpg",
"images/standard_scaler_logo_double.jpg",
"images/standard_scaler_logo_purple.jpg"
]
}
df = pd.DataFrame(data_as_dict)
df.head()

A nice feature in the pandas dataframe is that you can export it in html format with the command df.to_html()
Another very cool stuff is that you can provide formatters to the to_html function. In other words we can use our previous function image_formatter_html to transform the column logo_path. For example images/standard_scaler_logo_black.jpg will become <img src="images/standard_scaler_logo_black.jpg" width=150>
# Don't forget to set the `escape = False` argument otherwise
# that won't work. The formatters argument takes a dictionary as input
# it must have the following format {"column_name": formatting_function}
html_code = df.to_html(formatters={'logo_path': image_formatter_html}, escape = False)
HTML(html_code)

Isn't it beautiful ? :) . It will always amaze me how it can get me so excited to implement such basic stuff when we live in a world where super impressive things like ChatGPT exist :D
That being said, let's write the function that puts all the things together.
def render_images_inside_df(df, path_column):
"""render images inside a dataframe by providing the column
where we can find paths or urls to the images"""
html_code = df.to_html(formatters={path_column: image_formatter_html}, escape = False)
return HTML(html_code)
render_images_inside_df(df, path_column='logo_path')

You can leave it as is if it suits you.
If you want to refine a little more the function I can suggest you some more stuff.
For instance we can add the img_width argument so that we can set the size of the images.
def render_images_inside_df(df, path_column, img_width=150):
"""render images inside a dataframe by providing the column where we can find
paths or urls to the images"""
def image_formatter_html(path_or_url):
"""html formatter for images. It takes as input an image path or
a image url."""
html_code = f'<img src="{path_or_url}" width="{img_width}">'
return html_code
return HTML(df.to_html(formatters={'logo_path': image_formatter_html}, escape = False))
Note that there is some sophistication here, we define an inner function image_formatter_html.
Why is that so ? Recall that formatters takes a dictionary as input, it has to be in following format {"column_name": formatting_func}
The thing is we can't pass any extra argument to formating_func. To address this problem we define an inner function so that we can have access to img_width without explicitly setting an extra argument to image_formatter_html.
render_images_inside_df(df, path_column='logo_path', img_width=50)

from IPython.core.display import HTML
import pandas as pd
def render_images_inside_df(df, path_column, img_width=150):
"""render images inside a dataframe by providing the column where we can find
paths or urls to the images"""
def image_formatter_html(path_or_url):
"""html formatter for images. It takes as input an image path or
a image url."""
html_code = f'<img src="{path_or_url}" width="{img_width}">'
return html_code
return HTML(df.to_html(formatters={'logo_path': image_formatter_html}, escape = False))
If you are not familiar with the concept of classes in python, I recommend you to read on the subject before diving into this last paragraph. However if you are in a hurry or lazy, in a nutshell a class is a code template for creating objects. Objects have member variables and have behavior associated with them.
The idea here is to subclass pd.DataFrame and to add a function named render_images so that we can invoke it simply with a nasty df.render_images(...)
import pandas as pd
# Subclassing pd.DataFrame. In other words we create a new class "dataframe_plus" that is going to inherit
# every existing functions of pd.DataFrame, we then add our own custom function
class dataframe_plus(pd.DataFrame):
def render_images(self, path_column, img_width=80):
return render_images_inside_df(self, path_column, img_width)
# Instantiate the dataframe with the brand new class "dataframe_plus"
# We use the same data `data_as_dict` as used in the beginning of the post.
df = dataframe_plus(data_as_dict)
# Now we can call the function directly within the object, how cool is that ?
df.render_images('logo_path')
from IPython.core.display import HTML
import pandas as pd
# Subclassing pd.DataFrame. In other words we create a new class "dataframe_plus" that is going to inherit
# every existing functions of pd.DataFrame, we then add our own custom function
class dataframe_plus(pd.DataFrame):
def render_images(self, path_column, img_width=80):
"""render images inside a dataframe by providing the column where we can find
paths or urls to the images"""
def image_formatter_html(path_or_url):
"""html formatter for images. It takes as input an image path or
a image url."""
html_code = f'<img src="{path_or_url}" width="{img_width}">'
return html_code
return HTML(self.to_html(formatters={'logo_path': image_formatter_html}, escape = False))
df = dataframe_plus(data_as_dict)
df.render_images('logo_path')
I hope you enjoyed this article ! See ya soon !