How to manually scale a continuous legend in a seaborn scatterplot? - data-visualization

I'm creating a scatterplot with seaborn like this:
plt.figure(figsize=(20,5))
ax = sns.scatterplot(x=x,
y=y,
hue=errors,
s=errors*20,
alpha=0.8,
edgecolors='w')
ax.set(xlabel='X', ylabel='Y')
ax.legend(title="Error (m)", loc='upper right')
My errors contain values between approximately 0.1 and 12.5. However, for my legend seaborn automatically generates labels 0, 5, 10, 15. This makes my algorithm look worse than it is. I would like to change the step size in the legend while maintaining a correct mapping between colors and error magnitudes. For example 0, 4, 8, 12.5. Is this possible?

Related

How to mask seaborn heatmap while keeping the colorbar range

I am trying to create a visualization of square matrix with seaborn heatmap, where all elements range between 0 and 1. However, I want to only show those greater than some threshold (ex. 0.5) and set other values to 0. Moreover, I want to set the range of colorbar to be shown between 0.5 and 1, but I do not want to adjust the full colormap to range between 0.5 and 1, but keep the original colormap range.
For example, I attach two examples that I tried:
1st example
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
# Make a random matrix composed of elements between 0 and 1
matrix=np.random.rand(20, 20)
mask_threshold=0.5
matrix_masked=np.zeros_like(matrix)
for i in range(len(matrix_masked)):
for j in range(len(matrix_masked)):
if matrix[i][j]<mask_threshold:
matrix[i][j]=0
else:
matrix_masked[i][j]=matrix[i][j]
# Plot random matrix with seaborn heatmap, however I want to mask elements whose values are less than 0.5
# while keeping the colorbar range consistent
fig, ax=plt.subplots(figsize=(24, 20))
sns.heatmap(matrix, cmap="seismic", ax=ax, vmin=0, vmax=1)
cbar=ax.collections[0].colorbar
cbar.ax.tick_params(labelsize=30)
cbar.ax.yaxis.label.set_size(30)
cbar.outline.set_linewidth(1.0)
plt.tight_layout()
plt.show();
In this first example, the visualized matrix exactly corresponds to what I originally wanted, but not the colorbar because it ranges between 0 and 1. Therefore, I tried changing the range of colorbar like in the second example.
2nd example
# Plot random matrix with seaborn heatmap, however I want to mask elements whose values are less than 0.5
# while keeping the colorbar range consistent
fig, ax=plt.subplots(figsize=(24, 20))
sns.heatmap(matrix_masked, cmap="seismic", ax=ax, vmin=0, vmax=1)
cbar=ax.collections[0].colorbar
cbar.ax.set_ylim(0.5, 1.0)
cbar.ax.tick_params(labelsize=30)
cbar.ax.set_yticklabels(labels=[0.5, 0.6, 0.7, 0.8, 0.9, 1.0], weight="bold")
cbar.ax.yaxis.label.set_size(30)
cbar.outline.set_linewidth(1.0)
plt.tight_layout()
plt.show();
In this 2nd example, the range of colorbar indeed changes like what I want, but the height of figure and colorbar does not match, which is serious problem.
How can I solve this problem?
Your second example seems fine on my end, but here is a simpler alternative:
Use the Reds cmap with vmin=0.5 (no need to alter the cbar's ylim and yticklabels)
Use the mask param to automatically mask thresholded values (no need to zero them out)
Set the "bad" and "under" colors for displaying masked values on the heatmap and colorbar
matrix = np.random.default_rng(0).random(size=(20, 20))
cmap = plt.cm.get_cmap('Reds').copy()
cmap.set_bad('midnightblue') # color of mask on heatmap
cmap.set_under('midnightblue') # color of mask on cbar
sns.heatmap(matrix,
cmap=cmap, vmin=0.5, vmax=1, # set cbar range from 0.5 to 1
mask=matrix < mask_threshold, # use "bad" color for thresholded values
cbar_kws={'extend': 'min'}) # extend cbar to show "under" color

Problem with text and annotation x and y coordinates changing while looping through subplots matplotlib

I would like to iterate through subplots, plot data, and annotate the subplots with either the text function or the annotation function in matplotlib. Both functions ask for x and y coordinates in order to place text or annotations. I can get this to work fine, until I plot data. Then the annotations and the text jump all over the place and I cannot figure out why.
My set up is something like this, which produces well-aligned annotations with no data:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
fig, ax=plt.subplots(nrows=3, ncols=3, sharex=True)
fig.suptitle('Axes ylim unpacking error demonstration')
annotation_colors=["red", "lightblue", "tan", "purple", "lightgreen", "black", "pink", "blue", "magenta"]
for jj, ax in enumerate(ax.flat):
bott, top = plt.ylim()
left, right = plt.xlim()
ax.text(left+0.1*(right-left), bott+0.1*(top-bott), 'Annotation', color=annotation_colors[jj])
plt.show
When I add random data (or my real data), the annotations jump:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
#Same as above but but with 9 random data frames plotted.
df_cols = ['y' + str(x) for x in range(1,10)]
df=pd.DataFrame(np.random.randint(0,10, size=(10,9)), columns=df_cols)
df['x']=range(0,10)
#Make a few columns much larger in terms of magnitude of mean values
df['y2']=df['y2']*-555
df['y5']=df['y5']*123
fig, ax=plt.subplots(nrows=3, ncols=3, sharex=True)
fig.suptitle('Axes ylim unpacking error demonstration')
annotation_colors=["red", "lightblue", "tan", "purple", "lightgreen", "black", "pink", "blue", "magenta"]
for jj, ax in enumerate(ax.flat):
ax.plot(df['x'], df['y'+str(jj+1)], color=annotation_colors[jj])
bott, top = plt.ylim()
left, right = plt.xlim()
ax.text(left+0.1*(right-left), bott+0.1*(top-bott), 'Annotation', color=annotation_colors[jj])
plt.show()
This is just to demonstrate the issue that is likely caused by my lack of understanding of how the ax and fig calls are working. It seems to me that the coordinates x and y of the ax.text call may actually apply to the coordinates of of the fig, or something similar. The end result is far worse with my actual data!!! In that case, some of the annotations end up miles above the actual plots and not even within the coordinates of any of the subplot axes. Others completely overlap! What I am misunderstanding?
Edit for more details:
I have tried Stef's solution of using axes coordinates of axes.text(0.1, 0.1, 'Annotation'...)
I get the following plot, which still shows the same problem of moving the text all over the place. Because I am running this example with random numbers, the annotations are moving randomly with every run - i.e. they are not just displaced in the subplots with different axis ranges (y2 and y5).
You can specify the text location in axes coordinates (as opposed to data coordinates as you did implicitely):
ax.text(.1, .1, 'Annotation', color=annotation_colors[jj], transform=ax.transAxes)
See the Transformations Tutorial for further information.

How to se BG color over an Histogram graph in matplotlb [duplicate]

I am making a scatter plot in matplotlib and need to change the background of the actual plot to black. I know how to change the face color of the plot using:
fig = plt.figure()
fig.patch.set_facecolor('xkcd:mint green')
My issue is that this changes the color of the space around the plot. How to I change the actual background color of the plot?
Use the set_facecolor(color) method of the axes object, which you've created one of the following ways:
You created a figure and axis/es together
fig, ax = plt.subplots(nrows=1, ncols=1)
You created a figure, then axis/es later
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1) # nrows, ncols, index
You used the stateful API (if you're doing anything more than a few lines, and especially if you have multiple plots, the object-oriented methods above make life easier because you can refer to specific figures, plot on certain axes, and customize either)
plt.plot(...)
ax = plt.gca()
Then you can use set_facecolor:
ax.set_facecolor('xkcd:salmon')
ax.set_facecolor((1.0, 0.47, 0.42))
As a refresher for what colors can be:
matplotlib.colors
Matplotlib recognizes the following formats to specify a color:
an RGB or RGBA tuple of float values in [0, 1] (e.g., (0.1, 0.2, 0.5) or (0.1, 0.2, 0.5, 0.3));
a hex RGB or RGBA string (e.g., '#0F0F0F' or '#0F0F0F0F');
a string representation of a float value in [0, 1] inclusive for gray level (e.g., '0.5');
one of {'b', 'g', 'r', 'c', 'm', 'y', 'k', 'w'};
a X11/CSS4 color name;
a name from the xkcd color survey; prefixed with 'xkcd:' (e.g., 'xkcd:sky blue');
one of {'tab:blue', 'tab:orange', 'tab:green', 'tab:red', 'tab:purple', 'tab:brown', 'tab:pink', 'tab:gray', 'tab:olive', 'tab:cyan'} which are the Tableau Colors from the ‘T10’ categorical palette (which is the default color cycle);
a “CN” color spec, i.e. 'C' followed by a single digit, which is an index into the default property cycle (matplotlib.rcParams['axes.prop_cycle']); the indexing occurs at artist creation time and defaults to black if the cycle does not include color.
All string specifications of color, other than “CN”, are case-insensitive.
One method is to manually set the default for the axis background color within your script (see Customizing matplotlib):
import matplotlib.pyplot as plt
plt.rcParams['axes.facecolor'] = 'black'
This is in contrast to Nick T's method which changes the background color for a specific axes object. Resetting the defaults is useful if you're making multiple different plots with similar styles and don't want to keep changing different axes objects.
Note: The equivalent for
fig = plt.figure()
fig.patch.set_facecolor('black')
from your question is:
plt.rcParams['figure.facecolor'] = 'black'
Something like this? Use the axisbg keyword to subplot:
>>> from matplotlib.figure import Figure
>>> from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
>>> figure = Figure()
>>> canvas = FigureCanvas(figure)
>>> axes = figure.add_subplot(1, 1, 1, axisbg='red')
>>> axes.plot([1,2,3])
[<matplotlib.lines.Line2D object at 0x2827e50>]
>>> canvas.print_figure('red-bg.png')
(Granted, not a scatter plot, and not a black background.)
Simpler answer:
ax = plt.axes()
ax.set_facecolor('silver')
If you already have axes object, just like in Nick T's answer, you can also use
ax.patch.set_facecolor('black')
The easiest thing is probably to provide the color when you create the plot :
fig1 = plt.figure(facecolor=(1, 1, 1))
or
fig1, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, facecolor=(1, 1, 1))
One suggestion in other answers is to use ax.set_axis_bgcolor("red"). This however is deprecated, and doesn't work on MatPlotLib >= v2.0.
There is also the suggestion to use ax.patch.set_facecolor("red") (works on both MatPlotLib v1.5 & v2.2). While this works fine, an even easier solution for v2.0+ is to use
ax.set_facecolor("red")
In addition to the answer of NickT, you can also delete the background frame by setting it to "none" as explain here: https://stackoverflow.com/a/67126649/8669161
import matplotlib.pyplot as plt
plt.rcParams['axes.facecolor'] = 'none'
I think this might be useful for some people:
If you want to change the color of the background that surrounds the figure, you can use this:
fig.patch.set_facecolor('white')
So instead of this:
you get this:
Obviously you can set any color you'd want.
P.S. In case you accidentally don't see any difference between the two plots, try looking at StackOverflow using darkmode.

Ticks position in heatmap with categorical data (seaborn)

I am trying to plot a confusion matrix of my predictions. My data is multi-class (13 different labels) so I'm using a heatmap.
As you can see below, my heat map looks generally okay but the labels are a bit out of position: y ticks should be a little lower and x ticks should be a bit more to the right. I want to move both axis ticks a bit so they will aligned with the center of each square.
my code:
sns.set()
my_mask = np.zeros((con_matrix.shape[0], con_matrix.shape[0]), dtype=int)
for i in range(con_matrix.shape[0]):
for j in range(con_matrix.shape[0]):
my_mask[i][j] = con_matrix[i][j] == 0
fig_dims = (10, 10)
plt.subplots(figsize=fig_dims)
ax = sns.heatmap(con_matrix, annot=True, fmt="d", linewidths=.5, cmap="Pastel1", cbar=False, mask=my_mask, vmax=15)
plt.xticks(range(len(party_names)), party_names, rotation=45)
plt.yticks(range(len(party_names)), party_names, rotation='horizontal')
plt.show()
and for reproduction purpose, here are con_matrix and party_names hard-coded:
import numpy as np
from matplotlib import pyplot as plt
import seaborn as sns
con_matrix = np.array([[55, 0, 0, 0,0, 0, 0,0,0,0,0,0,2], [0,199,0,0,0,0,0,0,0,0,2,0,1],
[0, 0,52,0,0,0,0,0,0,0,0,0,1],
[0,0,0,39,0,0,0,0,0,0,0,0,0],
[0,0,0,0,90,0,0,0,0,0,0,4,3],
[0,0,0,1,0,35,0,0,0,0,0,0,0],
[0,0,0,0,5,0,26,0,0,1,0,1,0],
[0,5,0,0,0,1,0,44,0,0,3,0,1],
[0,1,0,0,0,0,0,0,52,0,0,0,0],
[0,1,0,0,2,0,0,0,0,235,0,1,1],
[1,2,0,0,0,0,0,3,0,0,34,0,3],
[0,0,0,0,5,0,0,0,0,1,0,40,0],
[0,0,0,0,0,0,0,0,0,1,0,0,46]])
party_names = ['Blues', 'Browns', 'Greens', 'Greys', 'Khakis', 'Oranges', 'Pinks', 'Purples', 'Reds', 'Turquoises', 'Violets', 'Whites', 'Yellows']
I already tried to work with position argument of different axes, but it did not turn out well. Could not find an exactly answer in this site as well (at least not a solution that works for categorical data).
I'm new in visualization with seaborn, any improvement with explanations would be appreciated (not only for my problem but on my code & visualization as well).
You can shift both the ticklabels by 0.5 offset to have the desired alignment. To do so, I have used NumPy's arange that enables vectorized addition of 0.5 to the whole array.
plt.xticks(np.arange(len(party_names))+0.5, party_names, rotation=45)
plt.yticks(np.arange(len(party_names))+0.5, party_names, rotation='horizontal')

How do I change the color of the axes of a matplotlib 3D plot?

I have set
import matplotlib as mpl
AXES_COLOR = '#333333'
mpl.rc('axes', edgecolor=AXES_COLOR, labelcolor=AXES_COLOR, grid=True)
mpl.rc('xtick', color=AXES_COLOR)
mpl.rc('ytick', color=AXES_COLOR)
mpl.rc('grid', color=AXES_COLOR)
The color of the axes labels and the ticks are properly set both in 2D and in 3D. However, the edgecolor doesn't apply to 3D axes and they remain black. Likewise, the grid isn't affected.
I think figured out how to access the individual axes of a 3D plot:
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d # Needed for 3d projection.
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.w_zaxis # <- the z axis
The documentation mentions a property that we can use until the developers have finished refactoring their 3D code:
import pprint
pprint.pprint(ax.w_xaxis._AXINFO)
{'x': {'color': (0.95, 0.95, 0.95, 0.5),
'i': 0,
'juggled': (1, 0, 2),
'tickdir': 1},
'y': {'color': (0.9, 0.9, 0.9, 0.5),
'i': 1,
'juggled': (0, 1, 2),
'tickdir': 0},
'z': {'color': (0.925, 0.925, 0.925, 0.5),
'i': 2,
'juggled': (0, 2, 1),
'tickdir': 0}}
However, the color parameter changes the color of the background of the axes planes (between the wired of the grid), not the color of the edges of these planes.
Am I digging too deep ?
Instead of changing axis3d.py try this: ax.w_xaxis.line.set_color("red")
Turns out it's impossible since these values are hard-coded. This archived email from the matplotlib-users mailing list helped me. Here's the relevant part:
Unfortunately, you have stumbled upon one of the ugliness of the mplot3d
implementation. I am hoping to have more control available for the next
release. But right now, there is no way to turn off the axes spines
(because they aren't implemented as spines). If you really want to dig into
the source code, you could change the color argument to the Line2D call in
the init3d() method in matplotlib/lib/mpl_toolkits/axis3d.py
Although this answer was addressing another concern, it sent me to the direction of axis3d.py. I found it in /usr/lib/pymodules/python2.7/mpl_toolkits/mplot3d. I made a backup of the original axis3d.py and I moved axis3d.pyc away.
Since the code is pretty short and fairly well written it didn't take long to locate the two lines I had to change.
To change the color of the edges of the individual axes, I modified the self.line=... in __init__: just replace color=(0, 0, 0, 1) by color=(1, 0, 0, 1) for a horribly flashy red. Components of the tuple are red, green, blue, alpha, all floats from 0 to 1.
To change the color of the grid, I modified the draw method. I replaced the color self.gridlines.set_color([(0.9,0.9,0.9,1)] * len(lines)) by something of my choosing.
And that's it, it just works. Not the most convenient, but it's not more work than editing a rc configuration file.
I did not recreate a .pyc file. It does not recreate itself because I do not run my python code as root. I don't mind the extra milliseconds that python needs to recompile the .py each time.