mwaskom / seaborn

Statistical data visualization in Python
https://seaborn.pydata.org
BSD 3-Clause "New" or "Revised" License
12.59k stars 1.93k forks source link

Controlling color of both line and bar at same time using object interface #3746

Closed maddytae closed 3 months ago

maddytae commented 3 months ago

Would it not be nice to have ability to have color specified for both event and region? I can pass all color in scale but that would mess up the legend.

(
    so.Plot(fmri,x= "timepoint", y="signal")
      .add(so.Line(color='blue'),so.Agg('mean'),linewidth="event",linestyle='event',)
      .add(so.Bar(), so.Agg(),color='region')
      .scale(color={'parietal':'red','frontal':'green'},
             linewidth={'cue':3,'stim':1},
            linestyle={'cue':'solid','stim':'dashed'})
)
Screenshot 2024-08-07 at 11 08 00 PM
maddytae commented 3 months ago

More context

We can change linestyle and linewidth of 'event'. However we can not control color of 'event' at individual event levels because color of individual regions are already controlled at individual region levels. We can control color os all events like blue in above example. We can also pass all elements of event and region to color dict in scale and that would control the color but will mess up the legend.

maddytae commented 3 months ago
import pandas as pd
import seaborn as sns
from plotnine import (
    ggplot, aes, geom_line, geom_bar, scale_color_manual,scale_fill_manual,
    scale_linetype_manual, scale_size_manual, theme_minimal
)

# Load the fmri dataset from seaborn
fmri = sns.load_dataset('fmri')

# Aggregate data for line plot
line_data = fmri.groupby(['timepoint', 'event']).signal.mean().reset_index()
bar_data = fmri.groupby(['timepoint', 'region']).signal.mean().reset_index()
# Create the plot
plot = (
    ggplot() +

    # Bar plot with aggregation, mapping color to 'region'
    geom_bar(
        aes(x='timepoint', y='signal', fill='region'),
        data=bar_data,
        stat='identity',
        position='stack',

    ) +
     # Line plot with aggregation by 'mean', mapping size, linetype, and color
    geom_line(
        aes(x='timepoint', y='signal', group='event', color='event', linetype='event', size='event'),
        data=line_data
    ) +
    # Custom color, linetype, and size scales
    scale_color_manual(values={'cue': 'blue', 'stim': 'black', 'parietal':'red','frontal':'green'}) +
    scale_fill_manual(values={'cue': 'blue', 'stim': 'black', 'parietal':'red','frontal':'green'}) +
    scale_size_manual(values={'cue': 3, 'stim': 1}) +
    scale_linetype_manual(values={'cue': 'solid', 'stim': 'dashed'}) 
)

plot
Screenshot 2024-08-08 at 9 42 59 PM
frfeng commented 3 months ago

I'm also interested in this. Like the OP mentioned the legend looks incorrect when specifying all the colors in scale.

import seaborn as sns
import seaborn.objects as so

fmri = sns.load_dataset('fmri')

(
    so.Plot(fmri, x="timepoint", y="signal")
      .add(so.Line(), so.Agg(), linewidth="event", linestyle='event', color='event')
      .add(so.Bar(), so.Agg(), color='region')
      .scale(color={'parietal':'red', 'frontal':'green',
                    'stim':'black', 'cue':'blue',
                   },
             linewidth={'cue':3, 'stim':1},
             linestyle={'cue':'solid', 'stim':'dashed'})
)

so

Also, in a scenario like this where we wish to specify in scale the mappings of a common visual property (the color in this case) in multiple layers, maybe a nested dictionary like below (currently it does not work) is more readable?

 color={
     'region': {'parietal':'red', 'frontal':'green'},
     'event': {'stim':'black', 'cue':'blue'},
       },
mwaskom commented 3 months ago

Sorry, this isn’t supported. Maybe one day, but somewhat unlikely.