How to plot a lineplot with dots on specific points with specific colors and linetypes for each line using seaborn?

Viewed 4981

I have the following dataframe

import pandas as pd



 data_tmp = pd.DataFrame({'x': [0,14,28,42,56, 0,14,28,42,56],
                         'y': [0, 0.003, 0.006, 0.008, 0.001, 0*2, 0.003*2, 0.006*2, 0.008*2, 0.001*2],
                         'cat': ['A','A','A','A','A','B','B','B','B','B'],
                         'color': ['#B5D8F0','#B5D8F0','#B5D8F0','#B5D8F0','#B5D8F0','#247AB2','#247AB2','#247AB2','#247AB2','#247AB2'],
                         'point': [14,14,14,14,14,28,28,28,28,28],
                         'linestyles':['-','-','-','-','-','--','--','--','--','--']})

I would like to produce a lineplot with different color and linestyles per cat. But I would like to give the specific color and linestyles per cat as they are defined in the dataframe. Lastly I would like to mark the points on each line with the same color.

I have only tried:

sns.lineplot(x="x", y="y", hue="cat", data=data_tmp)
sns.scatterplot(x="point",y="y",hue="cat", data=data_tmp[data_tmp.point==data_tmp.x])
plt.show()

Any ideas ?

3 Answers

Maybe you want to use matplotlib directly, like

import pandas as pd
import matplotlib.pyplot as plt


df = pd.DataFrame({'x': [0,14,28,42,56, 0,14,28,42,56],
                   'y': [0, 0.003, 0.006, 0.008, 0.001, 0*2, 0.003*2, 0.006*2, 0.008*2, 0.001*2],
                   'cat': ['A','A','A','A','A','B','B','B','B','B'],})


d = {"A" : {"color": '#B5D8F0', "markersize":  5, "linestyle": "-"},
     "B" : {"color": '#247AB2', "markersize": 10, "linestyle": "--"}}

for n, grp in df.groupby("cat"):
    plt.plot(grp.x, grp.y, marker="o", label=n, **d[n])

plt.legend()
plt.show()

enter image description here

This is how I could do this. You need to use the cat column to control the different plot parameters (color, style, marker size), and then create mapping objects (here dicts) that tell which parameter value to use for each category. The color is easy. The linestyle is harder, because Seaborn only offers dashes as a configurable parameter, which needs to be given in the advanced Matplotlib format of (segment, gap). The function matplotlib.lines._get_dash_pattern translates the string value (e.g. --) to this format, although the returned value needs to be handled with care. For the marker size, unfortunately lineplot does not offer the possibility to change the marker size with the category (even though you can change the marker style), so you need to use a scatterplot on top. The last bit is the legend, you probably want to disable it for the second plot, to avoid repeating it, but the problem is that the first legend will not have the markers in it. If that bothers you, you can still edit the legend manually. All in all, it could look like this:

import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns

# Converts a line style to a format acceptable by Seaborn
def get_dash_pattern(style):
    _, dash = mpl.lines._get_dash_pattern(style)
    return dash if dash else (None, None)

data_tmp = pd.DataFrame({
    'x': [0,14,28,42,56, 0,14,28,42,56],
    'y': [0, 0.003, 0.006, 0.008, 0.001, 0*2, 0.003*2, 0.006*2, 0.008*2, 0.001*2],
    'cat': ['A','A','A','A','A','B','B','B','B','B'],
    'color': ['#B5D8F0','#B5D8F0','#B5D8F0','#B5D8F0','#B5D8F0',
              '#247AB2','#247AB2','#247AB2','#247AB2','#247AB2'],
    'point': [14,14,14,14,14,28,28,28,28,28],
    'linestyles':['-','-','-','-','-','--','--','--','--','--']})
# Extract plot features as dicts
feats = (data_tmp[['cat', 'color', 'linestyles', 'point']]
         .set_index('cat').drop_duplicates().to_dict())
palette, dashes, sizes = feats['color'], feats['linestyles'], feats['point']
# Convert line styles to dashes
dashes = {k: get_dash_pattern(v) for k, v in dashes.items()}
# Lines
lines = sns.lineplot(x="x", y="y", hue="cat", style="cat", data=data_tmp,
                     palette=palette, dashes=dashes)
# Points
sns.scatterplot(x="x", y="y", hue="cat", size="cat", data=data_tmp,
                palette=palette, sizes=sizes, legend=False)
# Fix legend
for t, l in zip(lines.legend().get_texts(), lines.legend().get_lines()):
    l.set_marker('o')
    l.set_markersize(sizes.get(l.get_label(), 0) / t.get_fontsize())
plt.show()

Output:

Final plot

Here is my solution with the help of @jdehesa

I also put the legend outside of the plot here and some polishing to the labels

def get_dash_pattern(style):
    _, dash = mpl.lines._get_dash_pattern(style)
    return dash if dash else (None, None)

palette = dict(zip(data_tmp.cat, data_tmp.color))
dashes = dict(zip(data_tmp.cat, data_tmp.linestyles))
dashes = {k: get_dash_pattern(v) for k, v in dashes.items()}

ax = sns.lineplot(x="x", y="y", hue="cat", data=data_tmp, palette=palette, style='cat',  dashes=dashes)
ax = sns.scatterplot(x="point", y="y", hue="cat", data=data_tmp[data_tmp.point == data_tmp.x], palette=palette,
                     legend=False)

ax.set_title('title')
ax.set_ylabel('y label')
ax.set_xlabel('x label')
ax.legend(loc=(1.04, 0))
plt.show()

enter image description here

Related