Search code examples
pythonplotlypie-chart

Plotly: How to change legend for a go.pie chart without changing data source?


I am practising building a Pie Chart in Plotly Express using Python.
So, this is the Pie Chart that I made;
enter image description here

This chart was build from a file with two columns called

  1. gender with values of [0, 1, 2]
  2. count_genders with values of [total_count_0, total_count_1, total_count_2]

I am planning to add some description to those values; for instance

  • 0 - female
  • 1 - male
  • 2 - undefined

This is where I am currently stuck.
If I remember correctly if you want to change a label in the legend (at least in Choropleth map), you could manipulate the ticks located in colorscale bar. By manipulating them, you could rename the label about the data. Thus I am wondering if you could do the same in Pie chart?

My current code for this graph:

import pandas as pd
import plotly.express as px
            
'''
Pandas DataFrame:
'''
users_genders = pd.DataFrame({'gender': {0: 0, 1: 1, 2: 2},
               'count_genders': {0: 802420, 1: 246049, 2: 106}})

''' Pie Chart Viz '''
gender_distribution = px.pie(users_genders,
                             values='count_genders',
                             names='gender',
                             color_discrete_map={'0': 'blue',
                                                 '1': 'red',
                                                 '2': 'green'},
                             title='Gender Distribution <br>'
                                   'between 2006-02-16 to 2014-02-20',
                             hole=0.35)
gender_distribution.update_traces(textposition='outside',
                                  textinfo='percent+label',
                                  marker=dict(line=dict(color='#000000',
                                                        width=4)),
                                  pull=[0.05, 0, 0.03],
                                  opacity=0.9,
                                  # rotation=180
                                  )
gender_distribution.update_layout(legend=dict({'traceorder': 'normal'}
                                              # ticks='inside',
                                              # tickvals=[0, 1, 2],
                                              # ticktext=["0 - Female",
                                              #           "1 - Male",
                                              #           "2 - Undefined"],
                                              # dtick=3
                                              ),
                                   legend_title_text='User Genders')
gender_distribution.show()

I tried to add the ticks in the update_layout to no avail. It returns an error message about incorrect parameters. Would someone kindly help me fix this issue?

edit 1: In case I wasn't clear, I wanted to know if it's possible to modify the values displayed in the legend without changing the original values within the file. Many thanks for your time for those who are already kind enough to help me fix this issue!

edit 2: Add the imports and other prior details of the code, removing the Dropbox link.


Solution

  • If I'm understanding your question correctly, you'd like to change what's displayed in the legend without changing the names in your data source. There may be more elegant ways of doing this but I've put together a custom function newLegend(fig, newNames) that will do exactly that for you.

    So with a figure like this:

    enter image description here

    ...running:

    fig = newLegend(fig = fig, newNames = {'Australia':'Australia = Dangerous',
                                           'New Zealand' : 'New Zealand = Peaceful'})
    

    ...will give you:

    enter image description here

    I hope this is what you were looking for. Don't hesitate to let me know if not!

    Complete code:

    import plotly.express as px
    
    df = px.data.gapminder().query("continent == 'Oceania'")
    fig = px.pie(df, values='pop', names='country')
    fig.update_traces(textposition='inside')
    fig.update_layout(uniformtext_minsize=12, uniformtext_mode='hide')
    
    def newLegend(fig, newNames):
        for item in newNames:
            for i, elem in enumerate(fig.data[0].labels):
                if elem == item:
                    fig.data[0].labels[i] = newNames[item]
        return(fig)
    
    fig = newLegend(fig = fig, newNames = {'Australia':'Australia = Dangerous',
                                           'New Zealand' : 'New Zealand = Peaceful'})
    fig.show()
    

    Edit 1: Example with data sample from OP

    The challenge with your data was that genders were of type integer and not string. So the custom function tried to replace an element of one type with an element of another type. I've solved this by replacing the entire array containing your labels in one go instead of manipulating it element by element.

    Plot:

    enter image description here

    Complete code:

    import pandas as pd
    import plotly.express as px
    import numpy as np
    
    # custom function to change labels    
    def newLegend(fig, newNames):
        newLabels = []
        for item in newNames:
            for i, elem in enumerate(fig.data[0].labels):
                if elem == item:
                    #fig.data[0].labels[i] = newNames[item]
                    newLabels.append(newNames[item])
        fig.data[0].labels = np.array(newLabels)
        return(fig)
    
    '''
    Pandas DataFrame:
    '''
    users_genders = pd.DataFrame({'0': {0: 1, 1: 2}, 
                                  '802420': {0: 246049, 1: 106}})
    
    users_genders = pd.DataFrame({'gender':[0,1,2],
                                   'count_genders': [802420, 246049, 106]})
    
    ''' Pie Chart Viz '''
    gender_distribution = px.pie(users_genders,
                                 values='count_genders',
                                 names='gender',
                                 color_discrete_map={'0': 'blue',
                                                     '1': 'red',
                                                     '2': 'green'},
                                 title='Gender Distribution <br>'
                                       'between 2006-02-16 to 2014-02-20',
                                 hole=0.35)
    gender_distribution.update_traces(textposition='outside',
                                      textinfo='percent+label',
                                      marker=dict(line=dict(color='#000000',
                                                            width=4)),
                                      pull=[0.05, 0, 0.03],
                                      opacity=0.9,
                                      # rotation=180
                                      )
    gender_distribution.update_layout(legend=dict({'traceorder': 'normal'}
                                                  # ticks='inside',
                                                  # tickvals=[0, 1, 2],
                                                  # ticktext=["0 - Female",
                                                  #           "1 - Male",
                                                  #           "2 - Undefined"],
                                                  # dtick=3
                                                  ),
                                       legend_title_text='User Genders')
    
    # custom function set to work
    gender_distribution=newLegend(gender_distribution, {0:"0 - Female",
                                                        1:"1 - Male",
                                                        2: "2 - Undefined"})
    
    
    gender_distribution.show()