Background colors matplotlib - python

I made this picture with matplotlib. I would like to split the background in two slightly colorfull side with two legends in each of them "mu < mu_{0}" for the left and "\mu > \mu_{0}$ for the right.
Do you know how to do that ?
THanks and regards.

You can use plt.fill to specify the area of the graph to shade. You can also used plt.text to annotate the sections. Here's an example of this for a graph not too dissimilar to yours (symmetric around the y-axis and bounded above by 1 and below by 0):
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-np.pi, np.pi, num=100)
fig, ax = plt.subplots(1, 1)
ax.plot(x, np.abs(np.sin(x)))
# Get the left and right extent of the area to shade
LHS, RHS = ax.get_xlim()
# Specify the area to shade as the corners of the square we're interested in
ax.fill([0, RHS, RHS, 0], [0, 0, 1, 1], c='C1', alpha=0.3)
ax.fill([0, LHS, LHS, 0], [0, 0, 1, 1], c='C2', alpha=0.3)
ax.text(-np.pi/2, 0.4, '$x < 0$')
ax.text(np.pi/2, 0.4, '$x > 0$')

Related

matplotlib fill_between leaving gaps between regions

I'm trying to use fill_between to fill different regions of a plot, but I get gaps between the regions I'm trying to fill.
I've tried using interpolate=True, but this results in non rectangular shapes...
`
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
x = np.arange(0, 4 * np.pi, 0.01)
y = np.sin(x)
ax.plot(x, y, color='black')
threshold = 0.75
ax.axhline(threshold, color='green', lw=2, alpha=0.7)
ax.fill_between(x, 0, 1, where=y > threshold,
facecolor=(0.5,0,0,0.5), ec=None,transform=ax.get_xaxis_transform())
ax.fill_between(x, 0, 1, where=y <= threshold,
facecolor=(0,0.5,0,0.5), ec=None, transform=ax.get_xaxis_transform())
`
I've attched a zoomed in screenshot of the plot.
You could do one or both of the following:
use finer-grainded x values, e.g.x = np.arange(0, 4 * np.pi, 0.0001). This will remove the white stripes at full view, but if you zoom in they will re-appear at a certain zoom level.
first draw the green background without a where condition over the full x range and then plot the red sections at the required sections. In case of non-opaque colors as in the example you'll need to manually re-calculate the semitransparent color on the default white background to a fully opaque color:
x = np.arange(0, 4 * np.pi, 0.001)
# ...
ax.fill_between(x, 0, 1, facecolor=(0, 0.5, 0, 0.5), ec=None,
transform=ax.get_xaxis_transform())
ax.fill_between(x, 0, 1, where=y>threshold, facecolor=(0.75, 0.5, 0.5),
ec=None, transform=ax.get_xaxis_transform())
I found an alternative way of solving this problem, by using pcolormesh where the color array is 1xn:
C = np.reshape(np.array(trnsys_out["LCG_state"][:-1].values), (-1, 1)).T
x = trnsys_out.index
y = [Pmin, Pmax]
ctrl = ax2.pcolormesh(x, y, C, shading="flat", cmap="binary", alpha=0.5, vmin=0, vmax=5)

How to toggle points on and off on matplotlib 3D scatter plot?

SOLVED (see below)
On 2D matpotlib scatter plot I can turn on and off points by accessing _offsets property of scatter plot object and setting it's .mask attribute True/False for indexes of those points we want to show/hide like this:
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
import random
def TogglePoints(event, plot):
plot._offsets.mask = [ [1, 1], [1, 1], [1, 1], [0, 0], [0, 0], [0, 0] ]
plot.figure.canvas.draw()
x = [random.randint(-10, 10) for n in range(6)]
y = [random.randint(-10, 10) for n in range(6)]
ax = plt.axes()
sc = ax.scatter(x, y, marker='o', s=20, alpha=1)
ax_button = plt.axes([0.81, 0.01, 0.1, 0.05])
button= Button(ax_button, "Toggle")
button.on_clicked(lambda event: TogglePoints(event, sc))
plt.show()
When you click the "Toggle" button on the figure, points with indexes 0, 1, 2 will disappear. You can make them re-appear by setting _offsets.mask back to False and re-drawing plot.
This is what I want to achieve with matplotlib 3D scatter plot.
Using _offsets.mask = [ [1, 1], [1, 1], [1, 1], [0, 0], [0, 0], [0, 0] ] on 3D scatter plot doesn't seem to work.
Actually it alters type of underlying array from MaskedArray to numpy.ndarray for some reason (see: Numpy MaskedArray in matplotlib 3D scatter plot, turns into ndarray when called by PyQt5 button click).
I know that 3D scatter plots have _offsets3d property. However I don't know how I can use it to show/hide points on the plot. Or maybe there's some other way ?
Does anyone know how I can do that ?
Thanks to this post:
Get working alpha value of scatter points in mpl_toolkits.basemap.Basemap
I've found a workaround that serves my purpose.
It concerns setting alpha values of points with set_facecolors().
So the working code now looks like this:
...
import pandas as pd #added
def TogglePointsOFF(event, plot):
for n in range(3): # n = index of point
fc_colors[n, 3] = 0 # 4th value is alpha
plot.set_facecolors(fc_colors)
plot.figure.canvas.draw()
def TogglePointsON(event, plot):
for n in range(3): # n = index of point
fc_colors[n, 3] = 1 # 4th value is alpha
plot.set_facecolors(fc_colors)
plot.figure.canvas.draw()
#I've put it into DataFrame() so you can better see
df = pd.DataFrame()
df['label'] = ["data_"+str(n) for n in range(6)]
df['id'] = [1, 1, 1, 2, 2, 2]
['x'] = [random.randint(-10, 10) for n in range(6)]
['y'] = [random.randint(-10, 10) for n in range(6)]
['z'] = [random.randint(-10, 10) for n in range(6)]
colors = {1:'red', 2:'blue'} # to map colors with df 'id'
#plot points colored according to value of df['id']
ax = plt.axes()
sc = ax.scatter(df['x'], df['y'], df['z'], c=df['id'].map(colors), marker='o', s=20, depthshade=False)
global fc_colors #yeah yeah globals...
face_colors = sc._face_colors
ax_button = plt.axes([0.81, 0.01, 0.1, 0.05])
ax_button_1 = plt.axes([0.68, 0.01, 0.12, 0.05])
button= Button(ax_button, "OFF")
button_1= Button(ax_button_1, "ON")
button.on_clicked(lambda event: TogglePointsOFF(event, sc))
button_1.on_clicked(lambda event: TogglePointsON(event, sc))
plt.show()
Clicking buttons "ON" and "OFF" will hide/show group of points based on index.
I've tried using set_alpha() and passing iterable of alpha values like: [0, 0, 0, 1, 1, 1] however it seemed to work on random points and set alpha of incorrect points.
Also getting face_colors from get_facecolors() seemed to get colors with random index alignment. This may be connected why passing iterable with alpha values to set_alpha() didn't work. That's why I take colors of points from: sc._face_colors .
Thank you for your time.
WARNING! Be advised.
This doesn't work when you use any 'official' colormap like this:
sc = ax.scatter(df['x'], df['y'], df['z'], cmap='tab10, vmin=10, vmax=10, marker='o', s=20, depthshade=False)
For setting alpha of points as described above you have to "kind-off" make you own colormap mapping like it was done here:
c=df['id'].map(colors)
or, use Normalizer object to map any colormap to some custom values like this:
from matplotlib.colors import Normalize #added
#let's assume we have some score values coresponding with data points:
score = [random.uniform(0.101, 100.123) for n in range(6)]
#but we can use any iterable with numbers
norm = Normalize(min(score), max(score)
cmap = matplotlib.cm.get_cmap('Spectral') #get some built in colormap
colors = cmap(norm(score))
#now you can use colors as 'c' parameter:
sc = ax.scatter(df['x'], df['y'], df['z'], c=colors, marker='o', s=20, depthshade=False)
Remember! don't put any alpha parameter and use depthshade=False to prevent fading of points in the back of the plot.
I hope you found this usefull.
Keep scrolling.

Tick placement for radial plot with log-scale for r

I'm trying to create a radial plot with a logrithmic scale on the r-axis, but the tick labels for the theta are coming up inside the plot.
import numpy as np
from matplotlib import pyplot as plt
np.random.seed(1)
r = 10**(1 + 2*np.random.rand(36))
theta = 2 * np.pi * np.linspace(0, 1, 37)
fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
ax.plot(theta, r)
# We need to reset the minimum r-limit to avoid log(0)
ax.set_rlim(0.1, None)
ax.set_rscale('log')
plt.show()
The theta ticks are inside the figure, which doesn't look so bad here, but are hidden for e.g. a pcolormesh plot - for comparison, if I comment out the set_rlim and set_rscale lines, we get the following with the desired location for the ticks. (For anyone using dark mode, the image background is transparent so the ticks might not show inline.)
I've tried looking at the ax.get_xticklabels but the y-position (equivalently the r-position) is 0.
[Text(0.0, 0, '0°'),
Text(0.7853981633974483, 0, '45°'),
Text(1.5707963267948966, 0, '90°'),
Text(2.356194490192345, 0, '135°'),
Text(3.141592653589793, 0, '180°'),
Text(3.9269908169872414, 0, '225°'),
Text(4.71238898038469, 0, '270°'),
Text(5.497787143782138, 0, '315°')]
Interestingly, if you increate the upper rlim (e.g. ax.set_rlim([0.1, 1e5])) the ticks move right to the edge of the figure.
You can use Axes.tick_params() to set the pad distance between the ticks and labels:
fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
ax.plot(theta, r)
ax.set_rmin(0.1)
ax.set_rscale('log')
ax.tick_params(pad=35)

How to add counts of points as a label in a sparse scatter plot

I have sparse scatter plot to visualize the comparison of predicted vs actual values. The range of the values are 1-4 and there are no decimal points.
I have tried plotly so far with hte following code (but I can also use a matplotlib solution):
my_scatter = go.Scatter(
x = y_actual, y = y_pred, mode = 'markers',
marker = dict(color = 'rgb(240, 189, 89)', opacity=0.5)
)
This prints the graph nicely (see below). I use opacity to see the density at each point. I.e. if two points lie on top of each other, the point will be shown in darker color. However, this is not explanatory enough. Is it possible to add the counts at each point as a label? There are some overlaps at certain intersections. I want to display how many points intersects. Can this be done automatically using matplotlib or plotly?
This answer uses matplotlib.
To answer the initial question first: You need to find out how often the data produces a point at a given coordinate to be able to annotate the points. If all values are integers this can easily be done using a 2d histogram. Out of the hstogram one would then select only those bins where the count value is nonzero and annotate the respective values in a loop:
x = [3, 0, 1, 2, 2, 0, 1, 3, 3, 3, 4, 1, 4, 3, 0]
y = [1, 0, 4, 3, 2, 1, 4, 0, 3, 0, 4, 2, 3, 3, 1]
import matplotlib.pyplot as plt
import numpy as np
x = np.array(x)
y = np.array(y)
hist, xbins,ybins = np.histogram2d(y,x, bins=range(6))
X,Y = np.meshgrid(xbins[:-1], ybins[:-1])
X = X[hist != 0]; Y = Y[hist != 0]
Z = hist[hist != 0]
fig, ax = plt.subplots()
ax.scatter(x,y, s=49, alpha=0.4)
for i in range(len(Z)):
ax.annotate(str(int(Z[i])), xy=(X[i],Y[i]), xytext=(4,0),
textcoords="offset points" )
plt.show()
You may then decide not to plot all points but the result from the histogramming which offers the chance to change the color and size of the scatter points,
ax.scatter(X,Y, s=(Z*20)**1.4, c = Z/Z.max(), cmap="winter_r", alpha=0.4)
Since all values are integers, you may also opt for an image plot,
fig, ax = plt.subplots()
ax.imshow(hist, cmap="PuRd")
for i in range(len(Z)):
ax.annotate(str(int(Z[i])), xy=(X[i],Y[i]), xytext=(0,0), color="w",
ha="center", va="center", textcoords="offset points" )
Without the necesity to calculate the number of occurances, another option is to use a hexbin plot. This gives slightly inaccurate positions of the dots, du to the hexagonal binning, but I still wanted to mention this option.
import matplotlib.pyplot as plt
import matplotlib.colors
import numpy as np
x = np.array(x)
y = np.array(y)
fig, ax = plt.subplots()
cmap = plt.cm.PuRd
cmaplist = [cmap(i) for i in range(cmap.N)]
cmaplist[0] = (1.0,1.0,1.0,1.0)
cmap = matplotlib.colors.LinearSegmentedColormap.from_list('mcm',cmaplist, cmap.N)
ax.hexbin(x,y, gridsize=20, cmap=cmap, linewidth=0 )
plt.show()

How to plot pseudo-3d bar chart in matplotlib?

I'd like to prepare some statistics for my boss. The flat style of matplotlib bar chart would make them look cheap for those used to Excel charts, although for clarity, using styles like this probably should be avoided.
I'm not that far away, but I don't get how to give the right thickness of the bars:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
row = [0, 0, 0, 22, 0, 0, 4, 16, 2, 0, 4, 4, 12, 26]
length = len(row)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = np.arange(length)
y = np.zeros(14)
z = np.array(row)
width = 0.8
ax.bar3d(x, y, [0]*length, 0.5, 0.001, z)
ax.set_xticks(x + width/2)
ax.set_xticklabels(titles[2:], rotation=90)
ax.set_yticks(y)
ax.set_zlabel('count')
plt.show()
Result:
The thickness of the bars are set by the dx, dy arguments in ax.bar3d for which you have the values 0.5, 0.001. The issue, as I'm sure you noticed is that changing dy will change the length of the bar (in your case the untitled axis), but matplotlib helpfully rescales the y axis so the data fills it. This makes it look strange (I am assuming this is the problem, sorry if it isn't).
To remedy this you could set the y limits using ax.set_ylim(0, 0.002) (basically your y values go from 0->0.001). If you change either dy or the value of y given to bar3d which is currently 0, then you will need to update the limits accordingly.
Example:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
row = [0, 0, 0, 22, 0, 0, 4, 16, 2, 0, 4, 4, 12, 26]
length = len(row)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.bar3d(range(length), [0]*length, [0]*length, 0.5, 0.001, row)
ax.set_ylim(-0.005, 0.005)
plt.show()

Categories