8

I am currently trying to plot some pandas data via matplotlib/seaborn, however one of my column titles is particularly long and stretches out the plot. Consider the following example:

import random

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style('darkgrid')

random.seed(22)
fig, ax = plt.subplots()

df = pd.DataFrame({'Year': [2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016],
                   'One legend label': [random.randint(1,15) for _ in range(10)],
                   'A much longer, much more inconvenient, annoying legend label': [random.randint(1, 15) for _ in range(10)]})

df.plot.line(x='Year', ax=ax)
ax.legend(bbox_to_anchor=(1, 0.5))
fig.savefig('long_legend.png', bbox_inches='tight')

This produces the following graph: graph with wide legend

Is there any way that I can set the legend entries to wrap, either on a character or a length? I tried to use textwrap to rename the DataFrame columns prior to plotting like so:

import textwrap
[...]
renames = {c: textwrap.fill(c, 15) for c in df.columns}
df.rename(renames, inplace=True)
[...]

However, pandas seemed to ignore the newlines in the column names.

asongtoruin
  • 9,794
  • 3
  • 36
  • 47

2 Answers2

13

You can use textwrap.wrap in order to adjust your legend entries (found in this answer), then update them in the call to ax.legend().

import random
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
from textwrap import wrap

sns.set_style('darkgrid')

df = pd.DataFrame({'Year': [2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016],
                   'One legend label': [random.randint(1,15) for _ in range(10)],
                   'A much longer, much more inconvenient, annoying legend label': [random.randint(1, 15) for _ in range(10)]})

random.seed(22)
fig, ax = plt.subplots()

labels = [ '\n'.join(wrap(l, 20)) for l in df.columns]

df.plot.line(x='Year', ax=ax,)
ax.legend(labels, bbox_to_anchor=(1, 0.5))

plt.subplots_adjust(left=0.1, right = 0.7)
plt.show()

Which gives:

enter image description here

Update: As pointed out in the comments, the documentation says textwrap.fill() is shorthand for '\n'.join(wrap(text, ...)). Therefore you can instead use:

from textwrap import fill
labels = [fill(l, 20) for l in df.columns]
DavidG
  • 24,279
  • 14
  • 89
  • 82
  • This is a good answer, though I'll quote [the docs](https://docs.python.org/2/library/textwrap.html) in saying "`fill()` is shorthand for `'\n'.join(wrap(text, ...))`". If you can update your answer to reflect this, I'll mark it as answered. – asongtoruin Nov 01 '17 at 15:44
-1

As @Jan Zeiseweis mentioned you can use \n within text as many times as you want (e.g. 'A much longer,\n much more inconvenient,\n annoying legend label') And if you are flexible with this you can place the legend below the figure to get a better visualisation by specifying 2 columns:

ax.legend(bbox_to_anchor=(0.9, -0.15),ncol=2,fontsize=8)
xan
  • 119
  • 4
  • As I mentioned in the comments, my actual data comes from a csv file and as such I don't want to manually edit the column names. – asongtoruin Nov 01 '17 at 15:44