Home > Blockchain >  Plotly: bar plot with color red<0, green>0, divided by groups
Plotly: bar plot with color red<0, green>0, divided by groups

Time:01-26

Given a dataframe with 2 groups: (group1, group2), that have values > and < than 0: plot:

  • Bar plot
  • x = x
  • y = values, divided by group1, group2
  • color = red if value<0, green if value>0
  • legend shows group1, grou2 with different colors.

My current code however is not coloring as i would expect, and the legend is shown with the same color:

import pandas as pd
import numpy as np
import plotly.express as px 

df = pd.DataFrame( {
    "x" : [1,2,3],
    "group1" : [np.nan, 1, -0.5],
    "group2" : [np.nan, -0.2, 1],  
}).set_index("x")


df_ = df.reset_index().melt(id_vars = 'x')
fig = px.bar(df_, x='x', y='value', color='variable', barmode='group')
fig.update_traces(marker_color=['red' if val < 0 else 'green' for val in df_['value']], marker_line_color='black', marker_line_width=1.5)
fig.show()

OUT with indications of what i want to achieve: enter image description here

CodePudding user response:

Since this cannot be achieved with express, we use a graph object to draw a bar chart for each group. The logic for color determination by numerical values is changed for Group 1 and Group 2, changing the conditions for Group 1 and Group 2. The reason is that the legend will not be color-coded unless this is done.

import pandas as pd
import numpy as np
import plotly.express as px 
import plotly.graph_objects as go

df = pd.DataFrame( {
    "x" : [1,2,3],
    "group1" : [np.nan, 1, -0.5],
    "group2" : [np.nan, -0.2, 1],  
}).set_index("x")

df_ = df.reset_index().melt(id_vars = 'x')

fig = go.Figure()

fig.add_trace(go.Bar(x=df_.query('variable =="group1"')['x'],
                     y=df_.query('variable =="group1"')['value'],
                     marker_color=['red' if val < 0 else 'green' for val in df_.query('variable =="group1"')['value']],
                     marker_line_color='black',
                     marker_line_width=1.5,
                     name='group1',
                     #legendgroup='group1'
                    )
             )

fig.add_trace(go.Bar(x=df_.query('variable =="group2"')['x'],
                     y=df_.query('variable =="group2"')['value'],
                     marker_color=['green' if val > 0 else 'red' for val in df_.query('variable =="group2"')['value']],
                     marker_line_color='black',
                     marker_line_width=1.5,
                     name='group2',
                     #legendgroup='group2'
                    )
             )
fig.update_layout(barmode='group', xaxis=dict(title_text='x'), yaxis=dict(title_text='value'))
fig.show()

enter image description here

CodePudding user response:

If you want to color bar according their x value (and not variable group), you have to update traces individually (or you have to draw graph objects manually):

fig = px.bar(df_, x='x', y='value', color='variable', barmode='group')
fig.for_each_trace(
    lambda trace: trace.update(marker_color=np.where(df_.loc[df_['variable'].eq(trace.name), 'value'] < 0, 'red', 'green'))
)
fig.update_layout(showlegend=False)  # Hide legend because there is no distinct group
fig.show()

Output:

enter image description here

CodePudding user response:

To stick with plotly.express, I would add a column to your dataframe, e.g. df_['positive'] with a boolean, and then color your plot by this variable.

It would look like this:

import pandas as pd
import numpy as np
import plotly.express as px 

df = pd.DataFrame( {
    "x" : [1,2,3],
    "group1" : [np.nan, 1, -0.5],
    "group2" : [np.nan, -0.2, 1],  
}).set_index("x")

df_ = df.reset_index().melt(id_vars = 'x')
df_['positive'] = (df_['value']>=0)

fig = px.bar(df_, x='x', y='value',barmode = 'group',
                color='positive',
                color_discrete_map={
                    True: 'green',
                    False: 'red'
                    }
)
fig.update_traces(marker_line_color='black', marker_line_width=1.5)
fig.show('browser')

which yields the following : enter image description here

EDIT following comments

If you want to keep the colors AND the group distinction within plotly.express, one way could be to add patterns...

Solution 1 : Every combination has its legend entry

df = pd.DataFrame( {
    "x" : [1,2,3],
    "group1" : [np.nan, 1, -0.5],
    "group2" : [np.nan, -0.2, 1],  
}).set_index("x")

df_ = df.reset_index().melt(id_vars = 'x')
positive = (df_['value']>=0)
df_['positive'] = positive
df_['sign'] = ['positive' if x else 'negative' for x in df_['positive']]
# Each compbination of color and patterns
fig = px.bar(df_, x='x', y='value',barmode = 'group',
                color='sign',
                color_discrete_map={
                    'positive': 'green',
                    'negative': 'red'
                    },
                pattern_shape="variable")
fig.update_layout(legend_title="Groups & Signs", bargap=0.5,bargroupgap=0.1)
fig.show('browser')

which outputs the following enter image description here

Solution 2 : Legend only reflects patterns

# Only patterns in legend
fig = px.bar(df_, x='x', y='value', color='variable',
                barmode='group',pattern_shape="variable")
fig.update_layout(legend_title="Groups", bargap=0.5,bargroupgap=0.1)
fig.for_each_trace(
    lambda trace: trace.update(marker_color=np.where(df_.loc[df_['variable'].eq(trace.name), 'value'] < 0, 'red', 'green'))
)
fig.show('browser')

which outputs : enter image description here However I was not able to 'remove' the green color from the legend...

  • Related