How to add custom labels to graphs

Howdy folks;

I’m trying to do something similar to this graph using plotly:

But I’m having trouble figuring out how to create the custom label, like on the above graph.

The closest I’ve come is this:

But I’d like to do something more along the lines of the first graph. Does anyone know how to do this using plotly?

Many thanks!

Here is the code I’m using::

# Create an interactive graph to help visualize numerical data
@widgets.interact(
    numericalFeature = numericalFeatures_widget,
    marginalFeature = marginals_widget
)
def numercialInspector(marginalFeature, numericalFeature):
    # Create a histogram object
    fig = px.histogram(
        diamonds_num_df, 
        x = numericalFeature,
        marginal = marginalFeature
    )

    # Extract the mean of the dataset
    mean =  diamonds_num_df[numericalFeature].mean()

    # Create a text stream
    meanValue = f"mean = {mean:.2f}"

    # Add the annotation to the histogram
    fig.add_vline(
        x = mean,
        line_width = 2,
        line_dash = 'dot',
        line_color = 'red',
        annotation = {
            'font' : {
                'size' : 12,
                'family' : 'Times New Roman',
                'color': 'red',
            }
        },
        annotation_text = meanValue,
        annotation_position = 'top right'
    )

    # Extract the median of the dataset
    median =  diamonds_num_df[numericalFeature].median()

    # Create a text stream
    medianValue = f"median = {median:.2f}"

    # Add the annotation to the histogram
    fig.add_vline(
        x = median,
        line_width = 2,
        line_dash = 'dot',
        line_color = 'darkgreen',
        annotation = {
            'font' : {
                'size' : 12,
                'family' : 'Times New Roman',
                'color': 'darkgreen',
            }
        },
        annotation_text = medianValue,
        annotation_position = 'top left'
    )
    
    # Extract the mode of the dataset
    mode =  diamonds_num_df[numericalFeature].mode()[0]

    # Create a text stream
    modeValue = f"mode = {mode:.2f}"

    # Add the annotation to the histogram
    fig.add_vline(
        x = mode,
        line_width = 2,
        line_dash = 'dot',
        line_color = 'black',
        annotation = {
            'font' : {
                'size' : 12,
                'family' : 'Times New Roman',
                'color': 'black',
            }
        },
        annotation_text = modeValue,
        annotation_position = 'top left'
    
    )

    # Create a graph title
    graphTitle = f"Measures of central tendency for: {numericalFeature}"

    # Update the graph
    fig.update_layout(
        title_text = graphTitle,
        yaxis_title_text = 'Total Counts', # yaxis label
        height = 600,
        width = 1200
    )

    # Display the results
    return fig.show()

This suggests an approach, by adding the vertical line as a trace, rather than as a vline:

Thanks for this link!

Although it didn’t resolve my problem directly, it did help me figure things out.

Cheers!

–Igor

It took some digging, but I figured out how to do it. I’m using the ipywidget library so I’m going to show the full code here.

This is what my graph looks like now:

Because I’m using xref = “paper” and yref = “paper”, I was able to place text in a relative position, regardless of the x and y axis scale. I was also able to compensate for marginals!

Below is the code to reproduce.

Requirements
Python 3.11
Ipywidgets 8.1.2
plotly 5.22.0
sklearn 1.4.2

Required Libraries

You’ll need these libraries to run the code:

# Library to create and handle a tabular dataset
import pandas as pd
# Library to plot graphs
import plotly.express as px
# Use the scikit-learn Library to create categorical and numerical features
from sklearn.compose import make_column_selector as selector
# Library used to create interactive controls
import ipywidgets as widgets

Load Dataset

The following will fetch the csv file from my github and load it:

# Create a data frame
diamonds_df = pd.read_csv(
    'https://raw.githubusercontent.com/iacisme/diamonds/main/00_Data/diamonds.csv',
    index_col = 0
)

# Display the newly created data frame
diamonds_df

Create a Dataset of Numberical Features

# Create an object that will output a list of numerical columns from a dataset
numericalColumnsSlctr_obj = selector(
    dtype_exclude = object
)

# Create a list of numerical columns
numericalColumns_list = numericalColumnsSlctr_obj(diamonds_df)

# Create a categorical dataset
diamonds_num_df = diamonds_df[numericalColumns_list]

# Verify the data
diamonds_num_df

Create a Graph

The following scripts will create an interactive graph as shown in the screen-shots:

# Create an interactive widget to help in graphing
numericalFeatures_widget = widgets.Dropdown(
    options = numericalColumns_list,
    value = numericalColumns_list[0],
    description = 'Feature:',
    disabled = False,
)

# Create a list of marginals
marginals_list = [
    None,
    'box',
    'violin',
    'rug'
]

# Create an interactive widget to help in graphing
marginals_widget = widgets.ToggleButtons(
    options = marginals_list,
    value = marginals_list[0],
    button_style = '', # '', 'success', 'info', 'warning', 'danger'
    description = 'Marginals:',
    disabled = False,
)

# Create an interactive graph to help visualize numerical data
@widgets.interact(
    numericalFeature = numericalFeatures_widget,
    marginalFeature = marginals_widget
)
def numercialInspector(marginalFeature, numericalFeature):
    # Create a histogram object
    fig = px.histogram(
        diamonds_num_df, 
        x = numericalFeature,
        marginal = marginalFeature,
        color_discrete_sequence = px.colors.qualitative.Dark24,
        template = "plotly_white",
    )

    # Used to adjust the annotation (text positioning) when using a marginal plot 
    if marginalFeature != None:
        yOffset = 0.18
    else:
        yOffset = 0
    
    # Calculate the measures of central tendency for numerical features
    meanCalc = diamonds_num_df[numericalFeature].mean()
    medianCalc = diamonds_num_df[numericalFeature].median()
    modeCalc = diamonds_num_df[numericalFeature].mode()[0]
    
    # Bundle values in tuples
    centralTendancyName_tuple = ('Mean', 'Median', 'Mode')
    centralTendancy_tuple = (meanCalc, medianCalc, modeCalc)
    
    # Generate vertical lines and annotations
    for name, calc in zip(centralTendancyName_tuple, centralTendancy_tuple):
        
        # Used to customize the annotations and vertical lines
        match name:
            case 'Mean':
                tendencyColor = 'darkred'
                yCord = 0.97
                textString = f'<b>{name}</b>: {meanCalc:0.2f}'
            case 'Median':
                tendencyColor = 'darkgreen'
                yCord = 0.93
                textString = f'<b>{name}</b>: {medianCalc}'
            case 'Mode':
                tendencyColor = 'black'
                yCord = 0.89
                textString = f'<b>{name}</b>: {modeCalc}'
    
        # Add some vertical lines to show the mean, median, mode on the histogram
        fig.add_vline(
            x = calc,
            line_width = 2,
            line_dash = 'dot',
            line_color = tendencyColor,
        )

        # Add the annotation text using paper reference. See:
        # https://stackoverflow.com/questions/76046269/how-to-align-annotation-to-the-edge-of-whole-figure-in-plotly
        fig.add_annotation(
            text = textString,
            font = {
                'size' : 13,
                'family' : 'Times New Roman',
                'color': tendencyColor ,
            },
            xref = "paper", 
            yref = "paper",
            x = 0.90, 
            y = yCord - yOffset, 
            showarrow = False
        )
    
    # Create a graph title
    graphTitle = f"Measures of central tendency for {numericalFeature}"

    # Update the graph
    fig.update_layout(
        title_text = graphTitle,
        yaxis_title_text = 'Total Counts', # yaxis label
        height = 600,
        width = 1200
    )
    
    # Display the results
    return fig.show()    

I hope this helps others try to figure this out!