I know how to cycle through a list of colors in matplotlib. But is it possible to do something similar with line styles (plain, dotted, dashed, etc.)? I'd need to do that so my graphs would be easier to read when printed. Any suggestions how to do that?
Something like this might do the trick:
import matplotlib.pyplot as plt
from itertools import cycle
lines = ["-","--","-.",":"]
linecycler = cycle(lines)
plt.figure()
for i in range(10):
x = range(i,i+10)
plt.plot(range(10),x,next(linecycler))
plt.show()
Result:
Edit for newer version (v2.22)
import matplotlib.pyplot as plt
from cycler import cycler
#
plt.figure()
for i in range(5):
x = range(i,i+5)
linestyle_cycler = cycler('linestyle',['-','--',':','-.'])
plt.rc('axes', prop_cycle=linestyle_cycler)
plt.plot(range(5),x)
plt.legend(['first','second','third','fourth','fifth'], loc='upper left', fancybox=True, shadow=True)
plt.show()
For more detailed information consult the matplotlib tutorial on "Styling with cycler"
To see the output click "show figure"
The upcoming matplotlib v1.5 will deprecate color_cycle for the new prop_cycler feature: http://matplotlib.org/devdocs/users/whats_new.html?highlight=prop_cycle#added-axes-prop-cycle-key-to-rcparams
plt.rcParams['axes.prop_cycle'] = ("cycler('color', 'rgb') +"
"cycler('lw', [1, 2, 3])")
Then go ahead and create your axes and plots!
here's a few examples of using the cyclers to develop sets of styles
cyclers can be added to give compositions (red with '-', blue with '--', ...)
plt.rc('axes', prop_cycle=(cycler('color', list('rbgk')) +
cycler('linestyle', ['-', '--', ':', '-.'])))
direct use on Axes:
ax1.set_prop_cycle(cycler('color', ['c', 'm', 'y', 'k']) +
cycler('lw', [1, 2, 3, 4]))
cyclers can be multiplied (http://matplotlib.org/cycler/) to give a wider range of unique styles
for ax in axarr:
ax.set_prop_cycle(cycler('color', list('rbgykcm')) *
cycler('linestyle', ['-', '--']))
see also: http://matplotlib.org/examples/color/color_cycle_demo.html
If you want the change to be automatic you can add this two lines in
the axes.py file of matplotlib:
Look for that line:
self.color_cycle = itertools.cycle(clist)
and add the following line underneath:
self.line_cycle = itertools.cycle(["-",":","--","-.",])
And look for the line:
kw['color'] = self.color_cycle.next()
and add the line:
kw['linestyle'] = self.line_cycle.next()
I guess you can do the same for marker.
I usually use a combination of basic colors and linestyles to represent different data sets. Suppose we have 16 data sets, each four data sets belonging to some group (having some property in common), then it is easy to visualize when we represent each group with a common color but its members with different line styles.
import numpy as np
import matplotlib.pyplot as plt
models=['00','01', '02', '03', '04', '05', '06', '07', '08', '09', '10',\
'11', '12', '13', '14', '15', '16']
fig = plt.figure()
ax = fig.add_subplot(111)
x = np.linspace(-1,1,100)
y = np.sin(x)
clrs_list=['k','b','g','r'] # list of basic colors
styl_list=['-','--','-.',':'] # list of basic linestyles
for i in range(0,16):
clrr=clrs_list[i // 4]
styl=styl_list[i % 4]
modl=models[i+1]
frac=(i+1)/10.0
ax.plot(x,y+frac,label=modl,color=clrr,ls=styl)
plt.legend()
plt.show()
I use code similar to this one to cycle through different linestyles. By default colours repeat after 7 plots.
idx = 0
for ds in datasets:
if idx < 7:
plot(ds)
elif idx < 14:
plot(ds, linestyle='--')
else:
plot(ds, linestyle=':')
idx += 1
Similar to Avaris graphs but different....
import matplotlib.pyplot as plt
import numpy as np
#set linestyles (for-loop method)
colors=('k','y','m','c','b','g','r','#aaaaaa')
linestyles=('-','--','-.',':')
styles=[(color,linestyle) for linestyle in linestyles for color in colors]
#-- sample data
numLines=30
dataXaxis=np.arange(0,10)
dataYaxis=dataXaxis+np.array([np.arange(numLines)]).T
plt.figure(1)
#-----------
# -- array oriented method but I cannot set the line color and styles
# -- without changing Matplotlib code
plt.plot(datax[:,np.newaxis],datay.T)
plt.title('Default linestyles - array oriented programming')
#-----------
#-----------
# -- 'for loop' based approach to enable colors and linestyles to be specified
plt.figure(2)
for num in range(datay.sh![enter image description here][1]ape[0]):
plt.plot(datax,datay[num,:],color=styles[num][0],ls=styles[num][1])
plt.title('User defined linestyles using for-loop programming')
#-----------
plt.show()
Related
I am trying to manipulate hatch of a countplot by hue. Here is the plot code and the corresponding plot I drew:
ax = sns.countplot(
data=data, y='M_pattern', hue='HHFAMINC',
palette=color_palette, lw=0.5, ec='black',
)
plt.yticks(rotation=45, ha='right')
legend_labels, _ = ax.get_legend_handles_labels()
hatches = ['-', '+', 'x', '\\']
# Loop over the bars
for i,thisbar in enumerate(bar.patches):
# Set a different hatch for each bar
thisbar.set_hatch(hatches[i])
plt.legend(
legend_labels, [
'Less than 10,000$ to 50,000$',
'50,000$ to 100,000$',
'100,000 to 150,000$',
'More than 150,000'
]
, title='Income categories'
)
plt.ylabel('Mandatory trip pattern')
plt.show()
Is there an straightforward way to hatch each income category separately?
ax.containers contains a list of each group of bars. To access individual bars, you can first loop through the containers and then through each of the bars. Instead of working with enumerate, it is highly recommended to use zip to simultaneously loop through two lists.
To rename legend entries, a replace onto the elements of the hue columns makes sure the correspondence between value and long name keeps intact after the dataset would be updated.
Here is an example using seaborn's 'tips' dataset. Here 3*hatch_pattern makes the hatching 3 times as dense.
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
data = sns.load_dataset('tips')
ax = sns.countplot(
data=data.replace({'day': {'Thur': 'Thursday', 'Fri': 'Friday', 'Sat': 'Saturday', 'Sun': 'Sunday'}}),
y='time', hue='day',
palette='Set2', lw=0.5, ec='black')
plt.yticks(rotation=45, ha='right')
hatches = ['-', '+', 'x', '\\']
for hatch_pattern, these_bars in zip(hatches, ax.containers):
for this_bar in these_bars:
this_bar.set_hatch(3 * hatch_pattern)
ax.legend(loc='upper right', title='Days')
plt.show()
I am following the NMT with attention (https://github.com/tensorflow/tensorflow/blob/r1.13/tensorflow/contrib/eager/python/examples/nmt_with_attention/nmt_with_attention.ipynb) tutorial and I am applying it for my own use case. Unfortunately, when I try to plot the attention weigths, I get alignment problems of the x-axis if the input is too long (e.g. 14 instead of 7).
In this code block, the plotting works as expected:
import numpy as np
from matplotlib import pyplot as plt
def plot_attention():
attention = np.array([[7.78877574e-10, 4.04739769e-10, 6.65854022e-05, 1.63362725e-04,
2.85054208e-04, 8.50252633e-04, 4.58042100e-02],
[9.23501700e-02, 5.69618285e-01, 1.80586591e-01, 9.78111699e-02,
2.71992851e-02, 9.59911197e-03, 2.54837354e-03]])
sentence = ['<start>', 'hace', 'mucho', 'frio', 'aqui', '.', '<end>']
predicted_sentence = ['it', 's']
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(1, 1, 1)
ax.matshow(attention, cmap='viridis')
fontdict = {'fontsize': 14}
ax.set_xticklabels([''] + sentence, fontdict=fontdict, rotation=90)
ax.set_yticklabels([''] + predicted_sentence, fontdict=fontdict)
plt.show()
plot_attention()
but with more elements in the list "sentence", it seems to misalign:
def plot_attention():
attention = np.array([[7.78877574e-10, 4.04739769e-10, 6.65854022e-05, 1.63362725e-04,
2.85054208e-04, 8.50252633e-04, 4.58042100e-02, 7.78877574e-10, 4.04739769e-10, 6.65854022e-05, 1.63362725e-04,
2.85054208e-04, 8.50252633e-04, 4.58042100e-02],
[9.23501700e-02, 5.69618285e-01, 1.80586591e-01, 9.78111699e-02,
2.71992851e-02, 9.59911197e-03, 2.54837354e-03, 7.78877574e-10, 4.04739769e-10, 6.65854022e-05, 1.63362725e-04,
2.85054208e-04, 8.50252633e-04, 4.58042100e-02]])
sentence = ['<start>', 'hace', 'mucho', 'frio', 'aqui', '.', '<end>', '<start>', 'hace', 'mucho', 'frio', 'aqui', '.', '<end>']
predicted_sentence = ['it', 's']
fig = plt.figure(figsize=(20,10))
ax = fig.add_subplot(1, 1, 1)
ax.matshow(attention, cmap='viridis')
fontdict = {'fontsize': 14}
ax.set_xticklabels([''] + sentence, fontdict=fontdict, rotation=90)
ax.set_yticklabels([''] + predicted_sentence, fontdict=fontdict)
plt.show()
plot_attention()
I expect the x-axis to be perfectly aligned and that all elements of the x-axis are shown (not every second one as it is right now)
The problem is that you are only setting the tick-labels without specifying the positions of the ticks. Whenever you modify the tick labels, you should always first set the tick positions. So, do the following in your code
ax.set_xticks(range(len(sentence)))
ax.set_yticks(range(len(predicted_sentence)))
ax.set_xticklabels(sentence, fontdict=fontdict, rotation=90)
ax.set_yticklabels(predicted_sentence, fontdict=fontdict)
I am using ax.stem to draw lollipop plot in python. However, I found it difficult to assign different colors to each lollipop
as shown here
As you can see I have 2 categories "GWP" & "FDP".
In my project, each category should be divided into 4 subcategories "ingredient", "Waste", "energy" and "infrastructure". Therefore, I want to assign them different colors to indicate the subcategory.
There is a solution proposed here: https://python-graph-gallery.com/181-custom-lollipop-plot/
But this only teaches you how to change color for all lollipops.
And there is another solution: https://python-graph-gallery.com/183-highlight-a-group-in-lollipop/
But this one doesn't really use ax.stem.
Please let me know how to assign different colors to each lollipop.
(Also, I don't know somehow why my plot is displayed upside down. Also, the y axis does not align in order, and there is one dot not connected by a line. It displays correctly in my original plot though.)
Here is my code:
#%%
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.style.use('ggplot')
# my dataset
columns = np.array(['types', 'GWP100 (year)', 'FDP (year)'])
types = np.array(['Total (ingredient) per kg', 'Total (waste) per kg',
'energy (whole process) per kg', 'Infrastructure', 'Total (Total)']).reshape(5,1)
gwp = np.array([ 2.86982617e+02, 2.16824983e+02, 4.38920760e+01,
6.02400000e-02, 5.47759916e+02]).reshape(5,1)
fdp = np.array([ 1.35455867e+02, 7.02868322e+00, 1.26622560e+01,
1.64568000e-02, 1.55163263e+02]).reshape(5,1)
original_data = np.concatenate((types, gwp, fdp), axis = 1)
# produce dataframe
data = pd.DataFrame(original_data, columns = columns)
# types GWP100 (year) FDP (year)
#0 Total (ingredient) per kg 286.982617 135.455867
#1 Total (waste) per kg 216.824983 7.02868322
#2 energy (whole process) per kg 43.892076 12.662256
#3 Infrastructure 0.06024 0.0164568
#4 Total (Total) 547.759916 155.163263
#%% graph
fig = plt.figure(1, figsize =(8,6))
# 1st subplot
ax1 = fig.add_subplot(1,2,1)
gwp = data[data.columns[1]]
ax1.stem(gwp)
ax1.set_ylabel(r'kg CO$_2$-Eq', fontsize=10)
ax1.set_xlabel('GWP', fontsize=10)
# 2nd subplot
ax2 = fig.add_subplot(1,2,2)
fdp = data[data.columns[2]]
ax2.stem(fdp)
ax2.set_ylabel(r'kg oil-Eq', fontsize = 10)
ax2.set_xlabel('FDP', fontsize=10)
The stem currently consists of a couple of lines and a "line" consisting of dots on top. It has no option to colorize the lines separately within its interface.
You may replicate the stem plot to draw the lines manually with the color you like.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
columns = np.array(['types', 'GWP100 (year)', 'FDP (year)'])
types = np.array(['Total (ingredient) per kg', 'Total (waste) per kg',
'energy (whole process) per kg', 'Infrastructure', 'Total (Total)'])
gwp = np.array([ 2.86982617e+02, 2.16824983e+02, 4.38920760e+01,
6.02400000e-02, 5.47759916e+02])
fdp = np.array([ 1.35455867e+02, 7.02868322e+00, 1.26622560e+01,
1.64568000e-02, 1.55163263e+02])
# produce dataframe
data = pd.DataFrame([types,gwp,fdp], index = columns).transpose()
colors = list("bgryk")
fig, (ax, ax2) = plt.subplots(ncols=2)
for t, y, c in zip(data["types"], data["GWP100 (year)"],colors):
ax.plot([t,t], [0,y], color=c, marker="o", markevery=(1,2))
ax.set_ylim(0,None)
plt.setp(ax.get_xticklabels(), rotation=90)
fig.tight_layout()
plt.show()
A more efficient solution is of course to use a LineCollection in combination with a scatter plot for the dots.
fig, (ax, ax2) = plt.subplots(ncols=2)
segs = np.zeros((len(data), 2, 2))
segs[:,:,0] = np.repeat(np.arange(len(data)),2).reshape(len(data),2)
segs[:,1,1] = data["GWP100 (year)"].values
lc = LineCollection(segs, colors=colors)
ax.add_collection(lc)
ax.scatter(np.arange(len(data)), data["GWP100 (year)"].values, c=colors)
ax.set_xticks(np.arange(len(data)))
ax.set_xticklabels(data["types"], rotation=90)
ax.autoscale()
ax.set_ylim(0,None)
fig.tight_layout()
plt.show()
I will answer one of your main questions regarding the same coloring of the lines and markers category wise. There seems to be no direct option while calling ax1.stem() to specify the list of colors as per the official docs. In fact they say that the resulting plot might not be reasonable if one do so. Nevertheless, below is one trick to get things done your way.
The idea is following:
Get the objects (stemline) displayed on the subplot
Get the x-y data of the markers
Loop over the data and change the color of each stemline. Plot the marker individually with the same color as stemline. The colors is an array specifying the colors of your choice.
Following is the relevant part of the code:
# 1st subplot
ax1 = fig.add_subplot(1,2,1)
gwp = data[data.columns[1]]
colors = ['r', 'g', 'b', 'y', 'k']
_, stemlines, _ = ax1.stem(gwp)
line = ax1.get_lines()
xd = line[0].get_xdata()
yd = line[0].get_ydata()
# mec and mfc stands for markeredgecolor and markerfacecolor
for i in range(len(stemlines)):
plt.plot([xd[i]], [yd[i]], 'o', ms=7, mfc=colors[i], mec=colors[i])
plt.setp(stemlines[i], 'color', colors[i])
ax1.set_ylabel(r'kg CO$_2$-Eq', fontsize=10)
ax1.set_xlabel('GWP', fontsize=10)
# 2nd subplot
ax2 = fig.add_subplot(1,2,2)
fdp = data[data.columns[2]]
_, stemlines, _ = ax2.stem(fdp)
line = ax2.get_lines()
xd = line[0].get_xdata()
yd = line[0].get_ydata()
for i in range(len(stemlines)):
plt.plot([xd[i]], [yd[i]], 'o', ms=7, mfc=colors[i], mec=colors[i])
plt.setp(stemlines[i], 'color', colors[i])
This question already has answers here:
Is it possible to add hatches to each individual bar in seaborn.barplot?
(2 answers)
Closed 5 months ago.
I have a bar plot created using seaborn. For example, the plot can be created as follows:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
data1 = pd.DataFrame(np.random.rand(17,3), columns=['A','B','C']).assign(Location=1)
data2 = pd.DataFrame(np.random.rand(17,3)+0.2, columns=['A','B','C']).assign(Location=2)
data3 = pd.DataFrame(np.random.rand(17,3)+0.4, columns=['A','B','C']).assign(Location=3)
cdf = pd.concat([data1, data2, data3])
mdf = pd.melt(cdf, id_vars=['Location'], var_name=['Letter'])
ax = sns.barplot(x="Location", y="value", hue="Letter", data=mdf, errwidth=0)
ax.legend(loc='upper center', bbox_to_anchor=(0.5, 1.2), ncol=3, fancybox=True, shadow=True)
plt.show()
This gives the following plot
I would like to do customize the chart as follows:
Remove the face color (set it to a white color)
Add a hash pattern to the image to distinguish the groups
How can this be achieved?
Removing the face color is easy, just do ax.set_facecolor('w'), although this will make the grid lines invisible. I'd recommend using sns.set_style('whitegrid') before plotting instead, you'll get a white background with only horizontal grid lines in grey.
As for the different has patterns, this is a little trickier with seaborn, but it can be done. You can pass the hatch keyword argument to barplot, but it'll be applied to each bar, which doesn't really help you distinguish them. Unfortunately, passing a dictionary here doesn't work. Instead, you can iterate over the bars after they're constructed to apply a hatch. You'll have to calculate the number of locations, but this is pretty straightforward with pandas. It turns out that seaborn actually plots each hue before moving on to the next hue, so in your example it would plot all blue bars, then all green bars, then all red bars, so the logic is pretty straightforward:
num_locations = len(mdf.Location.unique())
hatches = itertools.cycle(['/', '//', '+', '-', 'x', '\\', '*', 'o', 'O', '.'])
for i, bar in enumerate(ax.patches):
if i % num_locations == 0:
hatch = next(hatches)
bar.set_hatch(hatch)
So the full script is
import itertools
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style('whitegrid')
data1 = pd.DataFrame(np.random.rand(17, 3), columns=['A', 'B', 'C']).assign(Location=1)
data2 = pd.DataFrame(np.random.rand(17, 3) + 0.2, columns=['A', 'B', 'C']).assign(Location=2)
data3 = pd.DataFrame(np.random.rand(17, 3) + 0.4, columns=['A', 'B', 'C']).assign(Location=3)
cdf = pd.concat([data1, data2, data3])
mdf = pd.melt(cdf, id_vars=['Location'], var_name=['Letter'])
ax = sns.barplot(x="Location", y="value", hue="Letter", data=mdf, errwidth=0)
num_locations = len(mdf.Location.unique())
hatches = itertools.cycle(['/', '//', '+', '-', 'x', '\\', '*', 'o', 'O', '.'])
for i, bar in enumerate(ax.patches):
if i % num_locations == 0:
hatch = next(hatches)
bar.set_hatch(hatch)
ax.legend(loc='upper center', bbox_to_anchor=(0.5, 1.1), ncol=3, fancybox=True, shadow=True)
plt.show()
And I get the output
Reference for setting hatches and the different hatches available: http://matplotlib.org/examples/pylab_examples/hatch_demo.html
Note: I adjusted your bbox_to_anchor for the legend because it was partially outside of the figure on my computer.
I'm currently trying to plot multiple date graphs using matplotlibs plot_date function. One thing I haven't been able to figure out is how to assign each graph a different color automatically (as happens with plot after setting axes.color_cycle in matplotlib.rcParams). Example code:
import datetime as dt
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
values = xrange(1, 13)
dates = [dt.datetime(2013, i, 1, i, 0, 0, 0) for i in values]
mpl.rcParams['axes.color_cycle'] = ['r', 'g']
for i in (0, 1, 2):
nv = map(lambda k: k+i, values)
d = mdates.date2num(dates)
plt.plot_date(d, nv, ls="solid")
plt.show()
This gives me a nice figure with 3 lines in them but they all have the same color. Changing the call to plot_date to just plot results in 3 lines in red and green but unfortunately the labels on the x axis are not useful anymore.
So my question is, is there any way to get the coloring to work with plot_date similarly easy as it does for just plot?
From this discussion in GitHub it came out a good way to solve this issue:
ax.plot_date(d, nv, ls='solid', fmt='')
as #tcaswell explained, this function set fmt='bo' by default, and the user can overwrite this by passing the argument fmt when calling plot_date().
Doing this, the result will be:
Despite the possible bug you've found you can workaround that and create the plot like this:
The code is as follows. Basically a plot() is added just after the plot_date():
values = xrange(1, 13)
dates = [dt.datetime(2013, i, 1, i, 0, 0, 0) for i in values]
mpl.rcParams['axes.color_cycle'] = ['r', 'g', 'r']
ax = plt.subplot(111)
for i in (0, 1, 2):
nv = map(lambda k: k+i, values)
d = mdates.date2num(dates)
ax.plot_date(d, nv, ls='solid')
ax.plot(d, nv, '-o')
plt.gcf().tight_layout()
plt.show()
Note that another 'r' was required because, despite not showing, the colors are indeed cycling in plot_date(), and without this the lines would be green-red-green.
This isn't a bug, but a design choice. There is a default argument in both plt.date_plot and axes.date_plot that sets the format to bo.
See https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/axes.py#L4145 and https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/pyplot.py#L2997
[left same comment on github]
The only thing that plot_date does for you over plot is some configuration of the axes formatters, which you can do by hand.
import datetime as dt
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
values = xrange(1, 13)
dates = [dt.datetime(2013, i, 1, i, 0, 0, 0) for i in values]
mpl.rcParams['axes.color_cycle'] = ['r', 'g', 'r']
ax = plt.subplot(111)
for i in (0, 1, 2):
nv = map(lambda k: k+i, values)
d = mdates.date2num(dates)
ax.plot(d, nv, '-o')
ax.xaxis_date()
plt.gcf().tight_layout()
plt.show()