Search code examples
pythonplotly

How do you set the coordinates of added annotations on a figure when the x axis is categorical?


I have a plot that I have made which has two different categories that is subdvided into three different groups. I have made calculations of the mean and median for each of these groups, but when I try to add annotate the figures with these numbers, they end up printing on top of each other, when I want each figure within the plot to be annotated with its respective mean and median. violin plot

So my code to make this plot currently looks like this:

fig = px.violin(CVs,
                y="cv %",
                x="group",
                color="method",
                box=True,
                points=False,
                hover_data=CVs.columns)



for i in CVs['method'].unique():
        for j in CVs['group'].unique():
            mean, median = np.round(CVs.loc[CVs['method']==i].agg({'cv %':['mean', 'median']}), 2)['cv %'].values
            fig.add_annotation(x=j, y=0,
                               yshift=-65,
                               text="Mean: {}%".format(mean),
                               font=dict(size=10),
                               showarrow=False)
            fig.add_annotation(x=j, y=0,
                               yshift=-75,
                               text="Median: {}%".format(median),
                               font=dict(size=10),
                               showarrow=False)



fig.update_traces(meanline_visible=True)
fig.update_layout(template='plotly_white', yaxis_zeroline=False, height=fig_height, width=fig_width)
iplot(fig)

From what I have read in the documentation (https://plotly.com/python/text-and-annotations/), it seems like you need indicate the coordinates of the added annotation using the parameters x and y.

I have tried to adhere to these parameters by setting y to 0 (since the y axis is numerical), and setting x to the pertinent group along the x axis (which is a categorical). However, as one can tell from the plot above, this doesn't seem to work. I have also tried setting x to a value that increments with each iteration of the for loop, but all the values I have tried (e.g. 1, 10, 0.1) haven't worked, the annotations keep printing on top of each other, just at different places along the x axis.

I want to have one set of annotations under each figure. Does anyone know how I can set this up?


Solution

  • Based on what you used (yshift) to adjust the annotation, I have done the same using xshift to move each of the labels below their respective plot. Note that you have fig_height and fig_width which was not provided, so I let plotly choose the size. You may need to adjust the offset a bit if figure is different. Hope this works.

    CVs = px.data.tips() ##Used tips db
    CVs.rename(columns={'sex': 'group', 'day':'method', 'total_bill': 'cv %'}, inplace=True) ##Replaced to names you have
    CVs = CVs[CVs.method != 'Thur'] ##Removed one as there were 4 days in tips
    
    fig = px.violin(CVs,
                    y="cv %",
                    x="group",
                    color="method",
                    box=True,
                    points=False,
                    hover_data=CVs.columns)
    
    x_shift = -100 ##Start at -100 to the left of the j location
    for i in CVs['method'].unique():
        for j in CVs['group'].unique():
            mean, median = np.round(CVs.loc[CVs['method']==i].agg({'cv %':['mean', 'median']}), 2)['cv %'].values
            fig.add_annotation(x=j, y=0,
                               yshift=-65, xshift = x_shift,
                               text="Mean: {}%".format(mean),
                               font=dict(size=10),
                               showarrow=False)
            fig.add_annotation(x=j, y=0,
                               yshift=-75, xshift = x_shift,
                               text="Median: {}%".format(median),
                               font=dict(size=10),
                               showarrow=False)
        x_shift = x_shift + 100 ##After each entry (healthy/sick in your case), add 100
    
    
    fig.update_traces(meanline_visible=True)
    fig.update_layout(template='plotly_white', yaxis_zeroline=False)#, height=fig_height, width=fig_width)
    
    #iplot(fig)
    

    Plot

    enter image description here