I have data on a time-series in a pandas DataFrame and I would like to have separate markers for the lines. So far, I have only managed to use the same marker for both lines by using the marker='o' argument.
I'm using the example from http://stanford.edu/~mwaskom/software/seaborn/tutorial/timeseries_plots.html#specifying-input-data-with-long-form-dataframes and I've copied my copied and pasted the code below.
How can I plot separate markers for each line?
import numpy as np
np.random.seed(9221999)
import pandas as pd
from scipy import stats
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(palette="Set2")
def gamma_pdf(x, shape, coef, obs_err_sd=.1, tp_err_sd=.001):
y = stats.gamma(shape).pdf(x) * coef
y += np.random.normal(0, obs_err_sd, 1)
y += np.random.normal(0, tp_err_sd, len(x))
return y
gammas = []
n_units = 20
params = [(5, 1), (8, -.5)]
x = np.linspace(0, 15, 31)
for s in range(n_units):
for p, (shape, coef) in enumerate(params):
y = gamma_pdf(x, shape, coef)
gammas.append(pd.DataFrame(dict(condition=[["pos", "neg"][p]] * len(x),
subj=["subj%d" % s] * len(x),
time=x * 2,
BOLD=y), dtype=np.float))
gammas = pd.concat(gammas)
sns.tsplot(gammas, time="time", unit="subj",
condition="condition", value="BOLD", marker="o")
plt.show()
You'll have to either call tsplot twice with each level of the condition variable, or you can plot this way and then do a post-hoc manipulation of the plot data:
ax = sns.tsplot(gammas, time="time", unit="subj",
condition="condition", value="BOLD", marker="o")
ax.lines[-1].set_marker("s")
Related
I want to adjust colobar scale from my current figure1 to the desired figure2 !!
My colorbar scale range is -1 to 1, but I want it in exponential form and for that I tried levels = np.linspace(-100e-2,100e-2) as well, but it also doesn't give the desired scale2
import xarray as xr
import numpy as np
import matplotlib.pyplot as plt
ds = xr.open_dataset('PL_Era_Tkt.nc')
wp = ds.w.mean(dim=['longitude','latitude']).plot.contourf(x='time',cmap='RdBu',add_colorbar=False,extend='both')
wpcb = plt.colorbar(wp)
wpcb.set_label(label='Pa.s${^{-1}}$',size=13)
plt.gca().invert_yaxis()
plt.title('Vertical Velocity',size=15)
My current scale
My desired scale
Since the data is not presented, I added normalized color bars with the data from the graph sample here. I think the color bar scales will also be in log format with this setup. Please note that the data used is not large, so I have not been able to confirm this.
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib as mpl
import matplotlib.ticker as ticker
import numpy as np
plt.style.use('seaborn-white')
def f(x, y):
return np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)
x = np.linspace(0, 5, 50)
y = np.linspace(0, 5, 40)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
fig, ax = plt.subplots()
ax.contourf(X, Y, Z, 20, cmap='RdGy')
cmap = mpl.cm.RdGy
norm = mpl.colors.Normalize(vmin=-1, vmax=1.0)
fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap),
ax=ax, orientation='vertical', label='Some Units', extend='both', ticks=ticker.LogLocator())
plt.show()
I would like to be able to add footnote text similar to the following in matplotlib:
The following code will create a plot with similar text
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
fig, ax = plt.subplots(figsize = (5, 8))
n = 10
np.random.seed(1)
_ = ax.scatter(np.random.randint(0, 10, n), np.random.randint(0, 10, n), s=500)
x = 0
y = 1
_ = ax.text(
x, y, "hello this is some text at the bottom of the plot", fontsize=15, color="#555"
)
Which looks as:
However, if the data changes then the above won't adjust, such as:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
fig, ax = plt.subplots(figsize=(5, 8))
n = 10
np.random.seed(2)
_ = ax.scatter(np.random.randint(0, 10, n), np.random.randint(0, 10, n), s=500)
x = 0
y = 1
_ = ax.text(
x, y, "hello this is some text at the bottom of the plot", fontsize=15, color="#555"
)
I have seen this question/answer, and this just says how to plot text at a particular x,y coordinate. I specifically want to be able to set a footnote though, not plot at a particular x,y, so the solution should be dynamic.
Also, use of the OOP interface is preferred as mentioned in the docs.
Note - there seems to be issues with the current suggestion when using fig.tight_layout()
You should try plotting the text relative to the subplot and not relative to the points in the subplot using transform=ax.transAxes. You should also set the alignment so that the text starts based on the location you want. The can play around with the point location.
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
fig, ax = plt.subplots(figsize=(5, 8))
n = 10
np.random.seed(2)
_ = ax.scatter(np.random.randint(0, 10, n), np.random.randint(0, 10, n), s=500)
x = 0
y = -.07
ax.text(x, y, "hello this is some text at the bottom of the plot", fontsize=15,
horizontalalignment='left',verticalalignment='top', transform=ax.transAxes)
plt.show()
Lets say I have this scatterplot and would like to keep the size of the dots in the plot but in the legend I would like to have the size denoted as 1,2,... instead of 50,100,...
import numpy as np
import matplotlib.pyplot as plt
N = 50
x = np.random.rand(N)
y = np.random.rand(N)
a2 = 300*np.random.rand(N)
sc = plt.scatter(x, y, s=a2, alpha=0.5)
plt.legend(*sc.legend_elements("sizes", num=6))
plt.show()
It depends. If the numbers you want to show are just arbitrary, i.e. unrelated to the actual sizes, you can supply a list of numbers as labels.
import numpy as np
import matplotlib.pyplot as plt
N = 50
x = np.random.rand(N)
y = np.random.rand(N)
a2 = 300*np.random.rand(N)
sc = plt.scatter(x, y, s=a2, alpha=0.5)
plt.legend(sc.legend_elements("sizes", num=6)[0], [1,2,3,4,5])
plt.show()
If, however, there is a relation between the numbers to show and some data,
import numpy as np
import matplotlib.pyplot as plt
N = 50
x = np.random.rand(N)
y = np.random.rand(N)
a3 = np.random.randint(1,6, size=N)
f = lambda a: 12*a**2 # function to calculate size from data
g = lambda s: np.sqrt(s/12) # inverse function to calc. data from size
sc = plt.scatter(x, y, s=f(a3), alpha=0.5)
plt.legend(*sc.legend_elements("sizes", num=5, func=g))
plt.show()
I want to start the curve with one color and progressively blend into another color until the end. The following function in my MCVE works, but surely, there has to be a better way I haven't found out about, yet?!
import numpy as np
import matplotlib.pyplot as plt
def colorlist(color1, color2, num):
"""Generate list of num colors blending from color1 to color2"""
result = [np.array(color1), np.array(color2)]
while len(result) < num:
temp = [result[0]]
for i in range(len(result)-1):
temp.append(np.sqrt((result[i]**2+result[i+1]**2)/2))
temp.append(result[i+1])
result = temp
indices = np.linspace(0, len(result)-1, num).round().astype(int)
return [result[i] for i in indices]
x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x)
colors = colorlist((1, 0, 0), (0, 0, 1), len(x))
for i in range(len(x)-1):
xi = x[i:i+1+1]
yi = y[i:i+1+1]
ci = colors[i]
plt.plot(xi, yi, color=ci, linestyle='solid', linewidth='10')
plt.show()
Not sure what "better way" refers to. A solution with less code, which would draw faster is the use of a LineCollection together with a colormap.
A colormap can be defined by two colors and any colors in between are automatically interpolated.
cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", [(1, 0, 0), (0, 0, 1)])
A LineCollection can be used to plot a lot of lines at once. Being a ScalarMappable it can use a colormap to colorize each line differently according to some array - in this case one may just use the x values for that purpose.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import LinearSegmentedColormap
x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x)
cmap = LinearSegmentedColormap.from_list("", [(1, 0, 0), (0, 0, 1)])
points = np.array([x, y]).T.reshape(-1,1,2)
segments = np.concatenate([points[:-1],points[1:]], axis=1)
lc = LineCollection(segments, cmap=cmap, linewidth=10)
lc.set_array(x)
plt.gca().add_collection(lc)
plt.gca().autoscale()
plt.show()
The drawback of this solution as can be see in the picture is that the individual lines are not well connected.
So to circumvent this, one may plot those points overlapping, using
segments = np.concatenate([points[:-2],points[1:-1], points[2:]], axis=1)
In the above the color is linearly interpolated between the two given colors. The plot therefore looks different than the one from the question using some custom interpolation.
To obtain the same colors as in the question, you may use the same function to create the colors used in the colormap for the LineCollection. If the aim is to simplify this function you may directly calculate the values as the square root of the color difference in the channels.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import LinearSegmentedColormap
x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x)
def colorlist2(c1, c2, num):
l = np.linspace(0,1,num)
a = np.abs(np.array(c1)-np.array(c2))
m = np.min([c1,c2], axis=0)
s = np.sign(np.array(c2)-np.array(c1)).astype(int)
s[s==0] =1
r = np.sqrt(np.c_[(l*a[0]+m[0])[::s[0]],(l*a[1]+m[1])[::s[1]],(l*a[2]+m[2])[::s[2]]])
return r
cmap = LinearSegmentedColormap.from_list("", colorlist2((1, 0, 0), (0, 0, 1),100))
points = np.array([x, y]).T.reshape(-1,1,2)
segments = np.concatenate([points[:-2],points[1:-1], points[2:]], axis=1)
lc = LineCollection(segments, cmap=cmap, linewidth=10)
lc.set_array(x)
plt.gca().add_collection(lc)
plt.gca().autoscale()
plt.show()
In response to a comment above: If you want to change the color depending on the y value, you can use the following code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib.colors import LinearSegmentedColormap
x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(x)
ynorm = (y - y.min()) / (y.max() - y.min())
def colorlist2(c1, c2, num):
l = np.linspace(0, 1, num)
a = np.abs(np.array(c1) - np.array(c2))
m = np.min([c1, c2], axis=0)
s = np.sign(np.array(c2) - np.array(c1)).astype(int)
s[s == 0] = 1
r = np.sqrt(np.c_[(l * a[0] + m[0])[::s[0]],
(l * a[1] + m[1])[::s[1]], (l * a[2] + m[2])[::s[2]]])
return r
cmap = LinearSegmentedColormap.from_list(
"", colorlist2((1, 0, 0), (0, 0, 1), 100))
colors = [cmap(k) for k in ynorm[:-1]]
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-2], points[1:-1], points[2:]], axis=1)
lc = LineCollection(segments, colors=colors, linewidth=10)
lc.set_array(x)
plt.gca().add_collection(lc)
plt.gca().autoscale()
plt.show()
This will output this graph:
Graph with color depending on y value
I would like to generate a series of histogram shown below:
The above visualization was done in tensorflow but I'd like to reproduce the same visualization on matplotlib.
EDIT:
Using plt.fill_between suggested by #SpghttCd, I have the following code:
colors=cm.OrRd_r(np.linspace(.2, .6, 10))
plt.figure()
x = np.arange(100)
for i in range(10):
y = np.random.rand(100)
plt.fill_between(x, y + 10-i, 10-i,
facecolor=colors[i]
edgecolor='w')
plt.show()
This works great, but is it possible to use histogram instead of a continuous curve?
EDIT:
joypy based approach, like mentioned in the comment of october:
import pandas as pd
import joypy
import numpy as np
df = pd.DataFrame()
for i in range(0, 400, 20):
df[i] = np.random.normal(i/410*5, size=30)
joypy.joyplot(df, overlap=2, colormap=cm.OrRd_r, linecolor='w', linewidth=.5)
for finer control of colors, you can define a color gradient function which accepts a fractional index and start and stop color tuples:
def color_gradient(x=0.0, start=(0, 0, 0), stop=(1, 1, 1)):
r = np.interp(x, [0, 1], [start[0], stop[0]])
g = np.interp(x, [0, 1], [start[1], stop[1]])
b = np.interp(x, [0, 1], [start[2], stop[2]])
return (r, g, b)
Usage:
joypy.joyplot(df, overlap=2, colormap=lambda x: color_gradient(x, start=(.78, .25, .09), stop=(1.0, .64, .44)), linecolor='w', linewidth=.5)
Examples with different start and stop tuples:
original answer:
You could iterate over your dataarrays you'd like to plot with plt.fill_between, setting colors to some gradient and the line color to white:
creating some sample data:
import numpy as np
t = np.linspace(-1.6, 1.6, 11)
y = np.cos(t)**2
y2 = lambda : y + np.random.random(len(y))/5-.1
plot the series:
import matplotlib.pyplot as plt
import matplotlib.cm as cm
colors = cm.OrRd_r(np.linspace(.2, .6, 10))
plt.figure()
for i in range(10):
plt.fill_between(t+i, y2()+10-i/10, 10-i/10, facecolor = colors[i], edgecolor='w')
If you want it to have more optimized towards your example you should perhaps consider providing some sample data.
EDIT:
As I commented below, I'm not quite sure if I understand what you want - or if you want the best for your task. Therefore here a code which plots besides your approach in your edit two smples of how to present a bunch of histograms in a way that they are better comparable:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.cm as cm
N = 10
np.random.seed(42)
colors=cm.OrRd_r(np.linspace(.2, .6, N))
fig1 = plt.figure()
x = np.arange(100)
for i in range(10):
y = np.random.rand(100)
plt.fill_between(x, y + 10-i, 10-i,
facecolor=colors[i],
edgecolor='w')
data = np.random.binomial(20, .3, (N, 100))
fig2, axs = plt.subplots(N, figsize=(10, 6))
for i, d in enumerate(data):
axs[i].hist(d, range(20), color=colors[i], label=str(i))
fig2.legend(loc='upper center', ncol=5)
fig3, ax = plt.subplots(figsize=(10, 6))
ax.hist(data.T, range(20), color=colors, label=[str(i) for i in range(N)])
fig3.legend(loc='upper center', ncol=5)
This leads to the following plots:
your plot from your edit:
N histograms in N subplots:
N histograms side by side in one plot: