0

How do I map categories to line styles in matplotlib?

Below is my attempt with the 'tips' data for illustration (please ignore the suitability of such a graph for this data).

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

    df = sns.load_dataset('tips')

    fig, ax1 = plt.subplots()

    ax2 = ax1.twinx()

    lines = {'Female':'dotted', 'Male':'solid'}
    styles = df['sex'].apply(lambda x: lines[x])

    ax1.plot(df['day'], df['total_bill'],  c='darkorange', linestyle=styles)
    ax2.plot(df['day'], df['tip'],  c='steelblue', linestyle=styles)

    ax1.set_xlabel('Day')
    ax1.set_ylabel('Total Bill', color='darkorange')
    ax2.set_ylabel('Tip', color='steelblue')

    plt.show()

I've has success mapping colors this way, but I can't get it to work with linestyles. This is the error I get:

ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all(). 

Edit:

Here is the full traceback

ValueError                                Traceback (most recent call last)
<ipython-input-28-271de52e7c48> in <module>
      8 styles = df['sex'].apply(lambda x: lines[x])
      9 
---> 10 ax1.plot(df['day'], df['total_bill'],  c='darkorange', linestyle=styles)
     11 ax2.plot(df['day'], df['tip'],  c='steelblue', linestyle=styles)
     12 

~/anaconda3/envs/py36v1/lib/python3.6/site-packages/matplotlib/axes/_axes.py in plot(self, scalex, scaley, data, *args, **kwargs)
   1663         """
   1664         kwargs = cbook.normalize_kwargs(kwargs, mlines.Line2D._alias_map)
-> 1665         lines = [*self._get_lines(*args, data=data, **kwargs)]
   1666         for line in lines:
   1667             self.add_line(line)

~/anaconda3/envs/py36v1/lib/python3.6/site-packages/matplotlib/axes/_base.py in __call__(self, *args, **kwargs)
    223                 this += args[0],
    224                 args = args[1:]
--> 225             yield from self._plot_args(this, kwargs)
    226 
    227     def get_next_color(self):

~/anaconda3/envs/py36v1/lib/python3.6/site-packages/matplotlib/axes/_base.py in _plot_args(self, tup, kwargs)
    403                 "non-matching shapes is deprecated.")
    404         for j in range(max(ncx, ncy)):
--> 405             seg = func(x[:, j % ncx], y[:, j % ncy], kw, kwargs)
    406             ret.append(seg)
    407         return ret

~/anaconda3/envs/py36v1/lib/python3.6/site-packages/matplotlib/axes/_base.py in _makeline(self, x, y, kw, kwargs)
    310         default_dict = self._getdefaults(set(), kw)
    311         self._setdefaults(default_dict, kw)
--> 312         seg = mlines.Line2D(x, y, **kw)
    313         return seg
    314 

~/anaconda3/envs/py36v1/lib/python3.6/site-packages/matplotlib/lines.py in __init__(self, xdata, ydata, linewidth, linestyle, color, marker, markersize, markeredgewidth, markeredgecolor, markerfacecolor, markerfacecoloralt, fillstyle, antialiased, dash_capstyle, solid_capstyle, dash_joinstyle, solid_joinstyle, pickradius, drawstyle, markevery, **kwargs)
    375 
    376         self.set_linewidth(linewidth)
--> 377         self.set_linestyle(linestyle)
    378         self.set_drawstyle(drawstyle)
    379 

~/anaconda3/envs/py36v1/lib/python3.6/site-packages/matplotlib/lines.py in set_linestyle(self, ls)
   1192 
   1193         # get the unscaled dashes
-> 1194         self._us_dashOffset, self._us_dashSeq = _get_dash_pattern(ls)
   1195         # compute the linewidth scaled dashes
   1196         self._dashOffset, self._dashSeq = _scale_dashes(

~/anaconda3/envs/py36v1/lib/python3.6/site-packages/matplotlib/lines.py in _get_dash_pattern(style)
     36         style = ls_mapper.get(style, style)
     37     # un-dashed styles
---> 38     if style in ['solid', 'None']:
     39         offset, dashes = None, None
     40     # dashed styles

~/anaconda3/envs/py36v1/lib/python3.6/site-packages/pandas/core/generic.py in __nonzero__(self)
   1553             "The truth value of a {0} is ambiguous. "
   1554             "Use a.empty, a.bool(), a.item(), a.any() or a.all().".format(
-> 1555                 self.__class__.__name__
   1556             )
   1557         )

ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
Thomas Matthew
  • 2,826
  • 4
  • 34
  • 58
  • The most valueable information of an error is in which line it occurs, can you share that as well. – Erfan Apr 08 '20 at 20:58
  • 1
    `styles` is a `pandas.core.series.Series`, how is `linestyle` supposed to know which value to use? – Trenton McKinney Apr 08 '20 at 20:58
  • I think you want `df['sex'].map(lines)` – Erfan Apr 08 '20 at 20:59
  • @Erfan that is more succinct, but doesn't resolve the `ValueError` issue. – Trenton McKinney Apr 08 '20 at 21:00
  • 1
    Setting line color nor line style seems to work this way. The segmented line has only one color and one style. You'd need to draw each line segment separately. Note that if you have 10 x-positions, there are 9 segments. Should the color corresponding to the start or the end of the segment be considered? – JohanC Apr 08 '20 at 21:12
  • @Erfan updated the question with the Traceback. Error at drawing the first plot. – Thomas Matthew Apr 09 '20 at 05:03
  • @JohanC I followed the answer here and was able to use a lambda function to map categories to colors: https://stackoverflow.com/questions/26139423/plot-different-color-for-different-categorical-levels-using-matplotlib (granted its a different plot) – Thomas Matthew Apr 09 '20 at 05:14
  • @ThomasMatthew With a scatter plot (or with a bar plot) there is a one-to-one correspondence between a row in the dataframe and the graphical element. With a line plot, the line is between positions from two successive rows. – JohanC Apr 09 '20 at 10:04

1 Answers1

0

Looks like you want to use a sns.lineplot() which allows you to split your dataset according to a categorical column and change the way each line is displayed (either by color using hue= or by style using style=.

df = sns.load_dataset('tips')
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()

lines = {'Female':(1,3), 'Male':''}
sns.lineplot(ax=ax1, data=df, x='day', y='total_bill', color='darkorange', style='sex', dashes=lines, estimator=None)
sns.lineplot(ax=ax2, data=df, x='day', y='tip',  color='steelblue', style='sex', dashes=lines, estimator=None)

ax1.set_xlabel('Day')
ax1.set_ylabel('Total Bill', color='darkorange')
ax2.set_ylabel('Tip', color='steelblue')

enter image description here

Diziet Asahi
  • 38,379
  • 7
  • 60
  • 75