Matplotlib: personalize imshow axis - python-3.x

I have the results of a (H,ranges) = numpy.histogram2d() computation and I'm trying to plot it.
Given H I can easily put it into plt.imshow(H) to get the corresponding image. (see http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.imshow )
My problem is that the axis of the produced image are the "cell counting" of H and are completely unrelated to the values of ranges.
I know I can use the keyword extent (as pointed in: Change values on matplotlib imshow() graph axis ). But this solution does not work for me: my values on range are not growing linearly (actually they are going exponentially)
My question is: How can I put the value of range in plt.imshow()? Or at least, or can I manually set the label values of the plt.imshow resulting object?
Editing the extent is not a good solution.

You can just change the tick labels to something more appropriate for your data.
For example, here we'll set every 5th pixel to an exponential function:
import numpy as np
import matplotlib.pyplot as plt
im = np.random.rand(21,21)
fig,(ax1,ax2) = plt.subplots(1,2)
ax1.imshow(im)
ax2.imshow(im)
# Where we want the ticks, in pixel locations
ticks = np.linspace(0,20,5)
# What those pixel locations correspond to in data coordinates.
# Also set the float format here
ticklabels = ["{:6.2f}".format(i) for i in np.exp(ticks/5)]
ax2.set_xticks(ticks)
ax2.set_xticklabels(ticklabels)
ax2.set_yticks(ticks)
ax2.set_yticklabels(ticklabels)
plt.show()

Expanding a bit on #thomas answer
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mi
im = np.random.rand(20, 20)
ticks = np.exp(np.linspace(0, 10, 20))
fig, ax = plt.subplots()
ax.pcolor(ticks, ticks, im, cmap='viridis')
ax.set_yscale('log')
ax.set_xscale('log')
ax.set_xlim([1, np.exp(10)])
ax.set_ylim([1, np.exp(10)])
By letting mpl take care of the non-linear mapping you can now accurately over-plot other artists. There is a performance hit for this (as pcolor is more expensive to draw than AxesImage), but getting accurate ticks is worth it.

imshow is for displaying images, so it does not support x and y bins.
You could either use pcolor instead,
H,xedges,yedges = np.histogram2d()
plt.pcolor(xedges,yedges,H)
or use plt.hist2d which directly plots your histogram.

Related

Is there a way to plot Matplotlib's Imshow against changing x-axis limits and y-axis limits?

I'm trying to visualize a numpy array using imshow() since its similar to imagesc() in MATLAB.
import numpy as np
import matplotlib.pyplot as plt
plt.imshow(np.random.rand(8, 90), aspect='auto')
The resulting figure is as below with automatic selection of x-axis limits & y-axis limits. How could I set the parameters to change the x-axis & y-axis, which are of 2 different sizes of array of elements?
For Eg: x-axis = [100,], y-axis = [15,]
I tried using extent, however I need to provide [x-axis_min, x-axis_max, y-axis_min, y-axis_max]. But the problem is, I am having an array of elements as x-axis limits and an array of elements as y-axis limits. I don't know how to set these arrays as limits?
Thanks
If I understand your question, then this should do the trick?
img = np.random.rand(8, 90)
x = np.linspace(2.2e10, 2.475e10, 100)
y = np.linspace(-0.007, 0.007, 15)
plt.figure()
plt.imshow(img, aspect='auto', extent=[np.min(x), np.max(x), np.max(y), np.min(y)])

How to change the font weight of individual colorbar labels?

I would like to have different font weights for each of my colorbar labels.
I have tried to let LaTeX format the labels in the following way:
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
im = ax.imshow(np.random.rand(50, 50)/20)
cbar = ax.figure.colorbar(im, ticks=np.arange(0, 0.05, 0.01))
cbar.ax.set_yticklabels([r'{\fontsize{50pt}{3em}\selectfont{}{0}}',
r'{\fontsize{40pt}{3em}\selectfont{}{0.01}}',
r'{\fontsize{30pt}{3em}\selectfont{}{0.03}}',
r'{\fontsize{20pt}{3em}\selectfont{}{0.03}}',
r'{\fontsize{10pt}{3em}\selectfont{}{0.04}}',
r'{\fontsize{1pt}{3em}\selectfont{}{0.05}}', ])
but this only updates the text of the labels to the whole string (e.g., {\fontsize{50pt}{3em}\selectfont{}{0}}). The pyplot TeX demo works for me. Even if this solution would work it would not be ideal as I would probably need to specify everything manually.
Much more convenient would be something like in this question. There, I learned that the font size of single labels of the regular x and y axis can be updated by calling
label = axes.yaxis.get_major_ticks()[2].label
label.set_fontsize(size)
replacing set_fontsize by set_fontweight correctly updates the weight of the selected label.
Unfortunately I could not find the equivalent of axes.yaxis.get_major_ticks()[2].label for the colorbar.
Is it possible to change the font weight of individual labels of the colorbar directly? With directly I mean without using a workaround like plotting some new text above existing labels.
If this is not possible a solution plotting text above existing labels which automatically uses the position and content the previous labels and only adjusts the font weight would also be appreciated.
Thanks!
As pointed out by #ImportanceOfBingErnest , set_fontweight works for setting the weight of single colorbar labels too.
I had to try a couple of things to find which call would give me the text objects defining the colorbar labels. They are accessible in cbar.ax.get_yticklabels().
The code snippet below now properly changes the weight of the second colorbar label:
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
im = ax.imshow(np.random.rand(50, 50)/20)
cbar = ax.figure.colorbar(im, ticks=np.arange(0, 0.05, 0.01))
cbar.ax.get_yticklabels()[1].set_fontweight(1000)
plt.show()
Output of code (not enough reputation for inline images)

Is there a maximum amount of ticklabels in a matplotlib axes?

So I have two lists one containing a bunch of years and the other one containing some integers, each list has 17 values.
when I make a simple bar chart
plt.bar(keys,values)
plt.show()
in the X axis of the graph it only contains some of the years in the keys list eg: the graph only has 2000,2002,2005,2007,2010,2012,2015. It has missed 2001,2003,2006,2008,2009 etc.
Is it because there is a maximum amount of keys allowed in the bar chart so it randomly took some keys?
If not how do i fix this?
There is a maximum amount of ticklabels on a matplotlib axes. This limit however lies well above 1000 and you would first run into severe lags when creating the figure.
The usual automatic ticking by matplotlib is that the axes are equipped with just as many labels as needed. I.e. if you plot 50 points on a plot, you would not want to have 50 labels as well. Further if you plot a point at 0.853164 you would not want to have such odd number being displayed as ticklabel on the axes.
Now, I cannot think of any reason matplotlib would produce the labels you report about, 2000,2002,2005,2007,2010,2012,2015, because the automatic locator for the ticks chooses equidistant points on the axes. For any help with this specific problem, a MCVE would be needed.
But in general there are two concepts from which you may choose.
Numerical axes
When plotting numbers, matplotlib will by default choose a linear axes and tick it automatically as described above.
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(2000, 2017)
y = np.random.randint(5,21, size=len(x))
plt.bar(x,y)
plt.show()
In this case an equidistant ticking of 2.5 units is chosen to have 7 nicely spaced ticks on the axes. If instead you would want to have every bar ticked, you could use a custom ticker.
E.g. a MultipleLocator with the interval set to 1,
import matplotlib.ticker as mticker
plt.gca().xaxis.set_major_locator(mticker.MultipleLocator(1))
plt.gca().tick_params(axis="x", rotation=90)
Or, a FixedLocator with the locations set to the x values of the bars,
import matplotlib.ticker as mticker
plt.gca().xaxis.set_major_locator(mticker.FixedLocator(x))
plt.gca().tick_params(axis="x", rotation=90)
Categorical axes
You may also decide that your xaxis shall be categorical. This means that every unique value gets its own tick and those ticks are equally spaced, independent of their value. This is easiest accomplished by converting the numbers to strings.
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(2000, 2017)
y = np.random.randint(5,21, size=len(x))
cats = list(map(str, x))
plt.bar(cats,y)
plt.gca().tick_params(axis="x", rotation=90)
plt.show()
The result is visually the same as above, but this time, the number 2000 is not at location 2000, but at its index 0, 2001 is at 1 and so on.
You can show all the ticks in this way:
plt.xticks(np.arange(min(keys), max(keys)+1, 1.0), rotation=45)
Example:
keys = [2000, 2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016]
values = range(1,18)
import matplotlib.pyplot as plt
plt.bar(keys,values)
plt.xticks(np.arange(min(keys), max(keys)+1, 1.0), rotation=45)
plt.show()

Issue with drawparallels argument in Basemap

This seems like it should be an easy fix but I can't get it to work. I would like 40°N to display in the attached plot, but setting the labels argument in drawparallels to [1,0,1,1] isn't doing the trick. That should plot the parallels lables where they intersect the left, top and bottom of the plot according to the documentation. I would also like for 0° to once again show up in the bottom right corner. Any idea of how I can fix those 2 issues?
from netCDF4 import Dataset as NetCDFFile
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.basemap import Basemap
from mpl_toolkits.basemap import addcyclic
nc = NetCDFFile('C:/myfile.nc')
lat = nc.variables['lat'][:]
lon = nc.variables['lon'][:]
time = nc.variables['time'][:]
olr = nc.variables['olr'][:]
olr,lon = addcyclic(olr,lon)
map = Basemap(llcrnrlon=0.,llcrnrlat=-40.,urcrnrlon=360.,urcrnrlat=40.,resolution='l')
lons,lats = np.meshgrid(lon,lat)
x,y = map(lons,lats)
levels = np.arange(-19.5,20.0,0.5)
levels = levels[levels!=0]
ticks = np.arange(-20.0,20.0,4.0)
cs = map.contourf(x,y,olr[0],levels, cmap='bwr')
cbar = plt.colorbar(cs, orientation='horizontal', cmap='bwr', spacing='proportional', ticks=ticks)
cbar.set_label('Outgoing Longwave Radiation Anomalies $\mathregular{(W/m^2)}$')
map.drawcoastlines()
map.drawparallels(np.arange(-40,40,20),labels=[1,0,1,1], linewidth=0.5, fontsize=7)
map.drawmeridians(np.arange(0,360,40),labels=[1,1,0,1], linewidth=0.5, fontsize=7)
The first part of the question is easy. In order for the label to show up, you have to actually draw the parallel, but np.arange(-40,40,20) does not include 40. So, if you change that statement to np.arange(-40,41,20) your 40N label will show up.
The second part should in principle be solvable in the same way, but Basemap apparently uses the modulo of the longitudes to compute the position of the labels, so just using np.arange(0,361,40) when drawing the meridians will result in two 0 labels on top of each other. However, we can capture the labels that drawmeridians generates and manually change the position of the second 0 label. The labels are stored in a dictionary, so they are easy to deal with. To compute the x position of the last label, I compute the difference in x-position between the first and the second label, multiply that with the amount of meridians to be drawn (360/40) and add the x-position of the first label.
Here the complete example:
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.basemap import Basemap
map = Basemap(llcrnrlon=0.,llcrnrlat=-40.,urcrnrlon=360.,urcrnrlat=40.,resolution='l')
map.drawcoastlines()
yticks = map.drawparallels(
np.arange(-40,41,20),labels=[1,0,1,1], linewidth=0.5, fontsize=7
)
xticks = map.drawmeridians(
np.arange(0,361,40),labels=[1,1,0,1], linewidth=0.5, fontsize=7
)
first_pos = xticks[0][1][0].get_position()
second_pos = xticks[40][1][0].get_position()
last_x = first_pos[0]+(second_pos[0]-first_pos[0])*360/40
xticks[360][1][0].set_position((last_x,first_pos[1]))
plt.show()
Here the resulting plot:
Hope this helps.

Take control of Seaborn marginal histograms?

Question 1:
How do I remove excess space in the plot, when plotting marginals? Answered below in first post.
Question 2:
How do I get more fine contorl over the margin histogram plots, e.g. to plot both histogram and decide kde parameters for the marginals? Answered below in second post, with JointGrid.
#!/usr/bin/env python3
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd
sns.set_palette("viridis")
sns.set(style="white", color_codes=True)
x = np.random.normal(0, 1, 1000)
y = np.random.normal(5, 1, 1000)
df = pd.DataFrame({"x":x, "y":y})
g = sns.jointplot(df["x"],df["y"], bw=0.15, shade=True, xlim=(-3,3), ylim=(2,8),cmap="coolwarm", kind="kde", stat_func=None)
# plt.tight_layout() # This will override seaborn parameters. Remember to exclude.
plt.show()
jointplot has a space parameter that determines the space between the mainplot and the marginplots.
Running this code:
g = sns.jointplot(df["x"],df["y"], bw=0.15, shade=True, xlim=(-3,3),
ylim=(2,8),cmap="coolwarm", kind="kde",
stat_func=None, space = 0)
plt.show()
results in this plot for me:
Please note that running with plt.tight_layout() will overrule the space argument for jointplot.
Edit:
To further specify the parameters of the marginal plot you can use marginal_kws. You must pass a dictionary that specifies the parameters of the kind of marginal plot you use.
In your example you use the kde plot as marginal plots. So I will continue to use that as an example:
Here I show how to change the kernel used to make the marginal plots.
g = sns.jointplot(df["x"],df["y"], bw=0.15, shade=True, xlim=(-3,3),
ylim=(2,8),cmap="coolwarm", kind="kde",
stat_func=None, space = 0, marginal_kws={'kernel': 'epa'})
plt.show()
resulting in this graph:
You can pass any parameter accepted by the kde plot as a key in the dictionary and the desired value for that parameter as the value of for that key.
Okay, so I'm going to go ahead and post an extra answer myself. It's not entirely apparent to me which parameters the extra marginal_kws can control. Instead, it might be more intuitive to build the plot layer-by-layer (especially coming from ggplot) using JointGrid:
g = sns.JointGrid(x="x", y="y", data=df) # Initiate multi-plot
g.plot_joint(sns.kdeplot) # Plot the center x/y plot as sns.kdeplot
g.plot_marginals(sns.distplot, kde=True) # Plot the edges as sns.distplot (histogram), where kde can be set to True

Resources