pythonplotlyfacet-grid

Adding two plotly line charts to the same facetgrid figure - python


I'm trying to add two separate traces using different y values to the exact same facet grid figure. The figure uses facet_col to separate subplots based on color. For each subplot the x-axis display cut as a categorical variable. I have two separate columns relating to price (price,price2).

I want to plot both of these values in the same facetgrid layout. But price2 trace should be displayed as a dashed line

1). They work individually but when trying to combine them in a single figure, the facetgrid layout is dismissed and a single plot is returned.

2). When combining the y-values in a single call, I distinguish between them by applying a dashed line to price2. But then the color sequence is lost and the legend is not updated.

import seaborn as sns
import plotly.express as px
import plotly.graph_objects as go
import numpy as np

diamonds = sns.load_dataset('diamonds')

diamonds['val'] = np.random.randint(1, 3, diamonds.shape[0])

diamonds['price2'] = diamonds['price'] + 5000

diamonds = diamonds.drop_duplicates(subset = ['cut','color'])

color_discrete_sequence = px.colors.qualitative.G10
fig1 = px.line(diamonds,
              x = "cut", 
              y = "price",
              color = "val", 
              facet_col = "color",
              facet_col_wrap = 3,
              facet_row_spacing = 0.08,
              facet_col_spacing = 0.08, 
              markers = True,
              color_discrete_sequence = color_discrete_sequence,
              category_orders = {"cut": ['E', 'I', 'J', 'H', 'F', 'G', 'D']}
              )

fig2 = px.line(diamonds,
              x = "cut", 
              y = "price2",
              color = "val", 
              facet_col = "color",
              facet_col_wrap = 3,
              facet_row_spacing = 0.08,
              facet_col_spacing = 0.08, 
              markers = True,
              color_discrete_sequence = color_discrete_sequence,
              category_orders = {"cut": ['E', 'I', 'J', 'H', 'F', 'G', 'D']}
              )

#update y-values for price2 only to be dashed
fig2.update_traces(patch={"line": {"dash": 'dot'}}) 

fig1_2 = go.Figure(data = fig1.data + fig2.data)

fig1_2.show()

enter image description here

2)

 fig = px.line(diamonds,
              x = "cut", 
              y = ["price","price2"],
              color = "val", 
              facet_col = "color",
              facet_col_wrap = 3,
              facet_row_spacing = 0.08,
              facet_col_spacing = 0.08, 
              markers = True,
              color_discrete_sequence = color_discrete_sequence,
              category_orders = {"cut": ['E', 'I', 'J', 'H', 'F', 'G', 'D']}
              )

for i in range(1,28,2):    
    fig.data[i]['line'] = dict(dash='dot') 

fig.show()

enter image description here

Edit 3:

enter image description here


Solution

  • To customize the line style, you will want to use a graph object. In the case of this question, you want to make each of the two y-values a different line type, so you can achieve this in the following way.

    import seaborn as sns
    import numpy as np
    from plotly.subplots import make_subplots
    import plotly.graph_objects as go
    import itertools
    
    np.random.seed(20231129)
    diamonds = sns.load_dataset('diamonds')
    diamonds['val'] = np.random.randint(1, 3, diamonds.shape[0])
    diamonds['price2'] = diamonds['price'] + 1000
    diamonds = diamonds.drop_duplicates(subset = ['cut','color'])
    
    lst = itertools.product(range(1,5),range(1,4))
    
    colors = ['E', 'I', 'J', 'H', 'F', 'G', 'D']
    sub_titles = [f'color={c}' for c in colors]
    
    fig = make_subplots(rows=4, cols=3,
                        start_cell="top-left",
                        shared_yaxes=True,
                        #shared_xaxes=True,
                        subplot_titles=sub_titles,
                       )
    
    for c,rc in zip(colors, lst):
        df_color = diamonds.query('color == @c')
        df_color.set_index('cut', inplace=True)
        df_color = df_color.reindex(index=['Ideal','Fair','Very Good','Premium','Good'])
        df_color.reset_index(inplace=True)
        for v in df_color['val'].unique():
            df = df_color.query('val == @v')
            if v == 1:
                color = 'blue'
                # dash = 'dot'
            else:
                color = 'red'
            #     dash = 'dot'
            fig.add_trace(go.Scatter(
                x=df['cut'],
                y=df['price'],
                mode='markers+lines',
                marker=dict(color=color),
                line=dict(dash='solid'),
                name=f'{v}'
            ), row=rc[0],col=rc[1])
            fig.add_trace(go.Scatter(
                x=df['cut'],
                y=df['price2'],
                mode='markers+lines',
                marker=dict(color=color),
                line=dict(dash='dot'),
                name=f'{v}'
            ), row=rc[0],col=rc[1])
    
    names = set()
    fig.for_each_trace(
        lambda trace:
            trace.update(showlegend=False)
            if (trace.name in names) else names.add(trace.name))
    
    # update xaxis and yaxis titles
    fig.layout['xaxis7']['title']['text']='cut'
    fig.layout['yaxis']['title']['text']='Value'
    fig.layout['yaxis4']['title']['text']='Value'
    fig.layout['yaxis7']['title']['text']='Value'
    
    fig.update_layout(height=450)
    fig.show()
    

    enter image description here