Scaling graphic without scaling labels, text and axis ticks etc - python
I have a problem with the scaling of my graphics. I use imshow to plot two matrices the first being a 2x2 matrix and the second a 5x5 matrix. I now what both to have the same size of the boxes representing the entries of the matrices. But I want them to be same in absolute size (like pixels).
If i plot both and compare them clearly the 2x2 matrix boxes are much bigger relative to the numbers inside than the 5x5 matrix.
2x2 matrix, too big boxes
5x5 matrix, right box size
I tried to use the "figsize" parameter of the plt.figure() function but this also rescales the numbers in the boxes.
Another thing I tried is the "extent" parameter of imshow, which did not work if i try to just make the boxes smaller. It just scaled them back up. (it works tho if i make the bounding box wider, then it automatically makes them thinner but that's not what i want, example below).
with use of extent: wider and thinner but I don't what that
Now again: I kind of want to resize the boxes but don't change the size of the text/numbers so that it does not look dump if i put the graphics right next to each other in an article. It does not have to be a way to automatically match the two figure box sizes, I'm already happy with any way to resize the boxes, because it does not have to be 100% accurate.
Anyone has an idea how i can do this ?
Thanks a lot already!!
Here is the code for the two graphics with quadratic boxes (what i want, but just changed sizes):
import matplotlib.pyplot as plt
import numpy as np
plt.style.use("seaborn-dark")
def gfx_1():
fig = plt.figure()
ax1 = plt.subplot(111)
data = [[1, 2], [3, 4]]
ax1.imshow(data, interpolation="nearest")
for (i, j), data in np.ndenumerate(data):
ax1.text(i, j, s=str(data), ha='center', va='center')
ax1.set_xticklabels([])
ax1.set_yticklabels([])
ax1.set_xticks(np.arange(-0.5, 1.5, 1.))
ax1.set_yticks(np.arange(-0.5, 1.5, 1.))
ax1.grid(linewidth=2)
plt.savefig("2x2.png")
def gfx_2():
fig = plt.figure()
ax1 = plt.subplot(111)
data = [[1, 2, 3, 4, 5], [3, 4, 5, 6, 7], [6, 7, 8, 9, 10], [9, 10, 11, 12, 13], [12, 13, 14, 15, 16]]
ax1.imshow(data, interpolation="nearest")
for (i, j), data in np.ndenumerate(data):
ax1.text(i, j, s=str(data), ha='center', va='center')
ax1.set_xticklabels([])
ax1.set_yticklabels([])
ax1.set_xticks(np.arange(-0.5, 4.5, 1.))
ax1.set_yticks(np.arange(-0.5, 4.5, 1.))
ax1.grid(linewidth=2)
plt.savefig("5x5.png")
and the modified one with extend (which i don't what):
def gfx_1():
fig = plt.figure()
ax1 = plt.subplot(111)
data = [[1, 2], [3, 4]]
ax1.imshow(data, interpolation="nearest", extent=(-0.5, 3.5, -0.5, 1.5))
for (i, j), data in np.ndenumerate(data):
ax1.text(i*2, j, s=str(data), ha='center', va='center')
ax1.set_xticklabels([])
ax1.set_yticklabels([])
ax1.set_xticks(np.arange(-0.5, 3.5, 2.))
ax1.set_yticks(np.arange(-0.5, 1.5, 1.))
ax1.grid(linewidth=2)
plt.savefig("2x2_wide.png")
I think you already found the correct answer by using figsize. Sure, the resulting image might look bigger with the 2x2 grid, but it's probably just a matter of zoom in your image visualization program. If you were to show them side by side at their native resolution, the squares would look the same size.
In other words, if you create a 2x2 grid in a 2 inches x 2 inches image, then each box would be a little smaller than 1 inch wide (because of the axes and everything else). If you create your 5x5 grid in a 5x5 inches images, then the boxes would still be roughly 1 inch wide
Here is created the two images with the below code, and copy-pasted them side by side in an image editor:
def gfx_1():
fig = plt.figure(figsize=(2,2))
ax1 = plt.subplot(111)
data = [[1, 2], [3, 4]]
ax1.imshow(data, interpolation="nearest")
for (i, j), data in np.ndenumerate(data):
ax1.text(i, j, s=str(data), ha='center', va='center')
ax1.set_xticklabels([])
ax1.set_yticklabels([])
ax1.set_xticks(np.arange(-0.5, 1.5, 1.))
ax1.set_yticks(np.arange(-0.5, 1.5, 1.))
ax1.grid(linewidth=2)
plt.savefig("./2x2.png")
def gfx_2():
fig = plt.figure(figsize=(5,5))
ax1 = plt.subplot(111)
data = [[1, 2, 3, 4, 5], [3, 4, 5, 6, 7], [6, 7, 8, 9, 10], [9, 10, 11, 12, 13], [12, 13, 14, 15, 16]]
ax1.imshow(data, interpolation="nearest")
for (i, j), data in np.ndenumerate(data):
ax1.text(i, j, s=str(data), ha='center', va='center')
ax1.set_xticklabels([])
ax1.set_yticklabels([])
ax1.set_xticks(np.arange(-0.5, 4.5, 1.))
ax1.set_yticks(np.arange(-0.5, 4.5, 1.))
ax1.grid(linewidth=2)
plt.savefig("./5x5.png")
gfx_1()
gfx_2()
Related
Prevent axes from cutting off dots in matplotlib scatter plots
import matplotlib.pyplot as plt x = [1, 2, 3, 4] y = [0, 1, 7, 2] plt.scatter(x, y, color='red') plt.title('number of iterations') plt.xlim([1, 4]) plt.ylim([1, 8]) If one was to plot this data, the dots on the axes are partially cut off. Is there a way to prevent this (i.e. can the dots be plotted on top of the axes)?
Setting the clip_on attribute to False allows you to go beyond the axes, but by default the axes will be on top. For example, the script x = [1, 2, 3, 4] y = [0, 1, 7, 2] plt.scatter(x, y, color="red", clip_on=False) plt.title('number of iterations') plt.xlim([1, 4]) plt.ylim([1, 8]) Yields the following. Note that the axes "cut through" the dots. If you want the dots to go on top of the axes/labels, you need to change the default zorder. For example, the script x = [1, 2, 3, 4] y = [0, 1, 7, 2] plt.scatter(x, y, color="red", clip_on=False, zorder = 10) plt.title('number of iterations') plt.xlim([1, 4]) plt.ylim([1, 8]) yields Note: any zorder value 3 or greater will work here.
Set clip_on to False: plt.scatter(x, y, color='red', clip_on=False)
Determine plot size with grid Matplotlib
I'm plotting some data using plt.scatter(), and I want to change the size of the plot which is it on, however the only results which come up when you search 'change plot size' are things to do with changing the figure's size, which I am not looking to do. To visualise my issue, I have a reproducible example where I'm trying to plot 4 points on a 10x10 grid, however the size of the scatter plot is determined by the data not the grid The two graphs above demonstrate my problem, I am trying to plot the four points on the left graph on the 10x10 grid seen on the right graph. I have added in a datapoint at (10, 10) to show this. My code is currently: x = [1, 2, 3, 4] y = [1, 2, 3, 4] fig = plt.figure() ax = fig.gca() ax.set_xticks(np.arange(0, 11, 1)) ax.set_yticks(np.arange(0, 11, 1)) plt.grid() plt.scatter(x, y) Which produces the left graph.
IIUC: x = [1, 2, 3, 4] y = [1, 2, 3, 4] fig = plt.figure() plt.xlim(0, 10) plt.ylim(0, 10) plt.grid() plt.scatter(x, y) Output:
Just change the limits of x and y axes: plt.xlim(0,11) plt.ylim(0,11)
Suggestions to plot overlapping lines in matplotlib?
Does anybody have a suggestion on what's the best way to present overlapping lines on a plot? I have a lot of them, and I had the idea of having full lines of different colors where they don't overlap, and having dashed lines where they do overlap so that all colors are visible and overlapping colors are seen. But still, how do I that.
I have the same issue on a plot with a high degree of discretization. Here the starting situation: import matplotlib.pyplot as plt grid=[x for x in range(10)] graphs=[ [1,1,1,4,4,4,3,5,6,0], [1,1,1,5,5,5,3,5,6,0], [1,1,1,0,0,3,3,2,4,0], [1,2,4,4,3,2,3,2,4,0], [1,2,3,3,4,4,3,2,6,0], [1,1,3,3,0,3,3,5,4,3], ] for gg,graph in enumerate(graphs): plt.plot(grid,graph,label='g'+str(gg)) plt.legend(loc=3,bbox_to_anchor=(1,0)) plt.show() No one can say where the green and blue lines run exactly and my "solution" import matplotlib.pyplot as plt grid=[x for x in range(10)] graphs=[ [1,1,1,4,4,4,3,5,6,0], [1,1,1,5,5,5,3,5,6,0], [1,1,1,0,0,3,3,2,4,0], [1,2,4,4,3,2,3,2,4,0], [1,2,3,3,4,4,3,2,6,0], [1,1,3,3,0,3,3,5,4,3], ] for gg,graph in enumerate(graphs): lw=10-8*gg/len(graphs) ls=['-','--','-.',':'][gg%4] plt.plot(grid,graph,label='g'+str(gg), linestyle=ls, linewidth=lw) plt.legend(loc=3,bbox_to_anchor=(1,0)) plt.show() I am grateful for suggestions on improvement!
Just decrease the opacity of the lines so that they are see-through. You can achieve that using the alpha variable. Example: plt.plot(x, y, alpha=0.7) Where alpha ranging from 0-1, with 0 being invisible.
imagine your panda data frame is called respone_times, then you can use alpha to set different opacity for your graphs. Check the picture before and after using alpha. plt.figure(figsize=(15, 7)) plt.plot(respone_times,alpha=0.5) plt.title('a sample title') plt.grid(True) plt.show()
Depending on your data and use case, it might be OK to add a bit of random jitter to artificially separate the lines. from numpy.random import default_rng import pandas as pd rng = default_rng() def jitter_df(df: pd.DataFrame, std_ratio: float) -> pd.DataFrame: """ Add jitter to a DataFrame. Adds normal distributed jitter with mean 0 to each of the DataFrame's columns. The jitter's std is the column's std times `std_ratio`. Returns the jittered DataFrame. """ std = df.std().values * std_ratio jitter = pd.DataFrame( std * rng.standard_normal(df.shape), index=df.index, columns=df.columns, ) return df + jitter Here's a plot of the original data from Markus Dutschke's example: And here's the jittered version, with std_ratio set to 0.1:
Replacing solid lines by dots or dashes works too g = sns.FacetGrid(data, col='config', row='outputs', sharex=False) g.map_dataframe(sns.lineplot, x='lag',y='correlation',hue='card', linestyle='dotted')
Instead of random jitter, the lines can be offset just a little bit, creating a layered appearance: import matplotlib.pyplot as plt from matplotlib.transforms import offset_copy grid = list(range(10)) graphs = [[1, 1, 1, 4, 4, 4, 3, 5, 6, 0], [1, 1, 1, 5, 5, 5, 3, 5, 6, 0], [1, 1, 1, 0, 0, 3, 3, 2, 4, 0], [1, 2, 4, 4, 3, 2, 3, 2, 4, 0], [1, 2, 3, 3, 4, 4, 3, 2, 6, 0], [1, 1, 3, 3, 0, 3, 3, 5, 4, 3]] fig, ax = plt.subplots() lw = 1 for gg, graph in enumerate(graphs): trans_offset = offset_copy(ax.transData, fig=fig, x=lw * gg, y=lw * gg, units='dots') ax.plot(grid, graph, lw=lw, transform=trans_offset, label='g' + str(gg)) ax.legend(loc='upper left', bbox_to_anchor=(1.01, 1.01)) # manually set the axes limits, because the transform doesn't set them automatically ax.set_xlim(grid[0] - .5, grid[-1] + .5) ax.set_ylim(min([min(g) for g in graphs]) - .5, max([max(g) for g in graphs]) + .5) plt.tight_layout() plt.show()
looking for marker text option for pyplot.plot()
I am looking for a way to insert numbers or text into markers. There is nothing in the matplotlib.pyplot.plot(*args, **kwargs) documentation about that. The default zoom level places markers on the edge, hence reducing the available space on which to inscribe text. import matplotlib.pyplot as plt x = [1, 2, 3, 4 ,5] y = [1, 4, 9, 6, 10] plt.plot(x, y, 'ro',markersize=23) plt.show()
As jkalden suggested, annotate would solve your problem. The function's xy-argument let you position the text so that you can place it on the marker's position. About your "zoom" problem, matplotlib will by default stretch the frame between the smallest and largest values you are plotting. It results in your outer markers having their centers on the very edge of the figure, and only half of the markers are visible. To override the default x- and y-limits you can use set_xlim and set_ylim. Here an offset is define to let you control the marginal space. import matplotlib.pyplot as plt x = [1, 2, 3, 4 ,5] y = [1, 4, 9, 6, 10] fig, ax = plt.subplots() # instanciate a figure and ax object # annotate is a method that belongs to axes ax.plot(x, y, 'ro',markersize=23) ## controls the extent of the plot. offset = 1.0 ax.set_xlim(min(x)-offset, max(x)+ offset) ax.set_ylim(min(y)-offset, max(y)+ offset) # loop through each x,y pair for i,j in zip(x,y): corr = -0.05 # adds a little correction to put annotation in marker's centrum ax.annotate(str(j), xy=(i + corr, j + corr)) plt.show() Here is how it looks:
This is a revision of #snake_charmer 's method. I used alignment options (instead of manual offsets) to center the text on the dot, and other options for color, size and boldness (weight) of the text. import matplotlib.pyplot as plt x = [1, 2, 3, 4 ,5] y = [1, 4, 9, 6, 10] fig, ax = plt.subplots() # instantiate a figure and ax object # annotate is a method that belongs to axes ax.plot(x, y, 'ro',markersize=23) ## controls the extent of the plot. offset = 1.0 ax.set_xlim(min(x)-offset, max(x)+ offset) ax.set_ylim(min(y)-offset, max(y)+ offset) # loop through each x,y pair for i,j in zip(x,y): ax.annotate(str(j), xy=(i, j), color='white', fontsize="large", weight='heavy', horizontalalignment='center', verticalalignment='center') plt.show()
You can do this using MathText. Here are the instructions from matplotlib.org fig, ax = plt.subplots() fig.subplots_adjust(left=0.4) marker_style.update(mec="None", markersize=15) markers = ["$1$", r"$\frac{1}{2}$", "$f$", "$\u266B$", r"$\mathcal{A}$"] for y, marker in enumerate(markers): # Escape dollars so that the text is written "as is", not as mathtext. ax.text(-0.5, y, repr(marker).replace("$", r"\$"), **text_style) ax.plot(y * points, marker=marker, **marker_style) format_axes(ax) fig.suptitle('mathtext markers', fontsize=14) plt.show()
Asnap hlines and vlines to whole pixels in matplotlib
I want to draw some hlines and vlines snapped to occupy whole pixels on the screen, not spread across several pixels (rendered, antialiased) as usual. Is there a transform T() so that vlines( T(x), T(ylo), T(yhi), linewidth=Twidth(.5) ) draws whole pixels ? Or, is there a way of telling some Mac backend (I use Qt4agg) to do this ?
Do you just want to turn antialiasing off? For example: import matplotlib.pyplot as plt x = [1, 4, 7] ylow = [0, 3, -2] yhigh = [1, 4, 2] width = [8, 15, 6] plt.vlines(x, ylow, yhigh, linewidth=width, antialiased=False) plt.axis([0, 8, -4, 5]) plt.show()