I am using matplotlib in Python and want to use the same plot but with several different axes that are all functions of the first one, but that do not linearly depend on the first y value.
As an example, let's assume a plot that shows a simple line y=x.
Now I have a random function like f(y)=5y^2 + 2.
My ideal output graph should now still be a line, but the equidistant ticks should not be y=1, 2, 3, 4, but f(y)=7, 22, 47, 82, so that I can overlay the two graphs with 2 different axes.
Is this even possible, as the distance between the ticks is not even nor can it be expressed in a log plot? Therefore I simply want to put a function on each tick value, without changing the graph nor the ticks' positions.
In a graphics program this would be straightforward, by simply using the same plot and manually rewriting each tick.
https://drive.google.com/file/d/1fp2vrFvlz-9xdJPmqdQjyMQK7gzPX24G/view?usp=sharing
Thank you in advance! The example code is not really helpful, as it is just the standard matplotlib code but the most important scaling part is missing.
I know that I can set the ticks manually with yticks, but this does not solve the scaling problem and all ticks would appear very close together.
plt.plot(["time_max_axis"], ["position_max_axis"])
plt.xlabel("Time (ms)")
plt.ylabel("Max position (mm)")
plt.ylim(0, z0_mm)
plt.show()
plt.plot(["time_max_axis"], ["frequency_axis"])
plt.xlabel("Oscillation frequency (kHz)")
plt.ylabel("Max position (mm)")
plt.ylim(fion_kHz, fion_kHz * (1 + (f_shift4 + f_shift6) / 100))
plt.show()
import matplotlib.pyplot as plt
from matplotlib.ticker import (MultipleLocator, AutoMinorLocator)
x = np.arange(50)
y = x/10 + np.random.rand(50)
fig, axs = plt.subplots(1,2, gridspec_kw={'width_ratios': [1, 20]})
plt.subplots_adjust(wspace=0, hspace=0)
axs[1].plot(x, y)
axs[1].plot(x, 2*y)
axs[1].plot(x, 3*y)
axs[1].grid()
axs[1].set_ylim(0)
axs[1].set_xlim(0)
axs[1].set_ylabel('max displacement $z_{max}$ (mm)')
ymin, ymax = axs[1].get_ylim()
majorlocator = ymax // 8 # 8 horizontal grid lines
ytickloc = np.arange(0, int(ymax), majorlocator)
axs[1].yaxis.set_major_locator(MultipleLocator(majorlocator))
ax1 = axs[1].twinx() # ghost axis of axs[1]
ax1.yaxis.set_ticks_position('left')
ax1.set_yticks([ymin, ymax])
ax1.set_yticklabels(['', f'$z_0$ = {round(ymax,2)}'])
axs[0].spines['top'].set_visible(False)
axs[0].spines['right'].set_visible(False)
axs[0].spines['bottom'].set_visible(False)
axs[0].spines['left'].set_visible(False)
axs[0].set_xticks([])
axs[0].set_yticks(ytickloc)
ytick2 = 5 * ytickloc**2 + 2 # f = 5y^2 + 2
ytick2 = list(ytick2)
ymin2 = ytick2[0]
ytick2[0] = ''
axs[0].set_yticklabels(ytick2)
axs[0].set_ylim(ymin, ymax)
axs[0].set_ylim(0)
axs[0].set_ylabel('Oscillation frequency $f_{osc}$ (kHz)')
ymax2 = 5 * ymax**2 + 2 # f = 5y^2 + 2
ax0 = axs[0].twinx() # ghost axis of axs[0]
ax0.yaxis.set_ticks_position('left')
ax0.spines['top'].set_visible(False)
ax0.spines['right'].set_visible(False)
ax0.spines['bottom'].set_visible(False)
ax0.spines['left'].set_visible(False)
ax0.set_yticks([ymin, ymax])
ax0.set_yticklabels([f'$\\bf{{f_{{ion}}}} = {round(ymin2, 2)}$', f'$f_{{max}}$ = {round(ymax2,2)}'])
plt.tight_layout()
Output:
The code below produces plots like this one:
I need to show only the tick labels in the y axis that are over the horizontal line. In this case, the labels [2,3,4,5] would need to be hidden. I've tried using
ax.get_yticks()
ax.get_yticklabels()
to retrieve the ticks that are drawn, and from those select only the ones above the y_min value to show. Neither command returns the actual tick labels drawn in the plot.
How can I do this?
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import FormatStrFormatter
# Some random data
x = np.random.uniform(1, 20, 100)
y = np.array(list(np.random.uniform(1, 150, 97)) + [4, 7, 9])
y_min = np.random.uniform(4, 10)
ax = plt.subplot(111)
ax.scatter(x, y)
ax.hlines(y_min, xmin=min(x), xmax=max(x))
ax.set_xscale('log')
ax.set_yscale('log')
ax.yaxis.set_minor_formatter(FormatStrFormatter('%.0f'))
ax.yaxis.set_major_formatter(FormatStrFormatter('%.0f'))
plt.show()
The tick labels are only available when the plot is effectively drawn. Note that the positions will change when the plot is interactively resized or zoomed in.
An idea is to add the test to the formatter function, so everything will stay OK after zooming etc.
The following example code uses the latest matplotlib, which allows to set a FuncFormatter without declaring a separate function:
import numpy as np
import matplotlib.pyplot as plt
x = np.random.uniform(1, 20, 100)
y = np.array(list(np.random.uniform(1, 150, 97)) + [4, 7, 9])
y_min = np.random.uniform(4, 10)
ax = plt.subplot(111)
ax.scatter(x, y)
ax.axhline(y_min) # occupies the complete width of the plot
ax.set_xscale('log')
ax.set_yscale('log')
ax.yaxis.set_minor_formatter(lambda x, t: f'{x:.0f}' if x >= y_min else None)
ax.yaxis.set_major_formatter(lambda x, t: f'{x:.0f}' if x >= y_min else None)
plt.show()
PS: You might use ax.tick_params(length=4, which='both') to set the same tick length for minor and major ticks.
You have to get current y tick labels:
fig.canvas.draw()
labels = [float(text.get_text()) for text in ax.yaxis.get_ticklabels(which = 'minor')]
Then apply the filter you need:
labels_above_threshold = [label if label >= y_min else '' for label in labels]
And finally set filtered labels:
ax.yaxis.set_ticklabels(labels_above_threshold, minor = True)
Complete Code
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import FormatStrFormatter
x = np.random.uniform(1, 20, 100)
y = np.array(list(np.random.uniform(1, 150, 97)) + [4, 7, 9])
y_min = np.random.uniform(4, 10)
fig, ax = plt.subplots()
ax.scatter(x, y)
ax.hlines(y_min, xmin=min(x), xmax=max(x))
ax.set_xscale('log')
ax.set_yscale('log')
ax.yaxis.set_minor_formatter(FormatStrFormatter('%.0f'))
ax.yaxis.set_major_formatter(FormatStrFormatter('%.0f'))
fig.canvas.draw()
# MINOR AXIS
labels = [int(text.get_text()) for text in ax.yaxis.get_ticklabels(which = 'minor')]
labels_above_threshold = [label if label >= y_min else '' for label in labels]
ax.yaxis.set_ticklabels(labels_above_threshold, minor = True)
# MAJOR AXIS
labels = [int(text.get_text()) for text in ax.yaxis.get_ticklabels(which = 'major')]
labels_above_threshold = [label if label >= y_min else '' for label in labels]
ax.yaxis.set_ticklabels(labels_above_threshold, minor = False)
plt.show()
we are building our reports on matplotlib. Each page has multiple charts and some text.
In the report data there is over 100 locations, each location has a density. The idea is to plot the points on a map where the color (shade of red) represents the density of the location.
However, I do not understand the connection between the kwargs : c and cmap in the ax.scatter call, nor do I understand the role of color.Normalize in this application.
import pandas as pd
import matplotlib
import numpy as np
from pandas import Series, DataFrame
import csv
from scipy import stats
import matplotlib.pyplot as plt
import random
import matplotlib.colors as colors
# Get the data and transform
data = pd.read_csv('logHistThis.csv')
data.drop('Unnamed: 0', axis=1, inplace=True)
dataMean = data['Density'].mean()
data = list(data['Density'])
# I was under the impresion that the data for the colormap
# had to be between 1 and 0 so did this:
aColorScale = []
def myColorScale(theData):
aColorScale = []
for x in theData:
this = x/100
aColorScale.append(this)
return aColorScale
aColorScale = myColorScale(data)
estimated_mu, estimated_sigma = stats.norm.fit(data)
xmin = min(data)
xmax = max(data)
x = np.linspace(xmin, xmax, 100)
pdf = stats.norm.pdf(x, loc=estimated_mu, scale=estimated_sigma)
thisRangeMin = np.log(27)
thisRangeMax = np.log(35)
q = [np.random.choice(data, 40)]
z = [ np.random.randint(1, 50, size=40)]
s = 100 *q
colormap = 'Reds'
normalize =matplotlib.colors.Normalize(vmin=xmin, vmax=xmax)
#plt.scatter(x,y,z,s=5, cmap=colormap, norm=normalize, marker='*')
fig = plt.figure(figsize=(10, 5), frameon=False, edgecolor='000000', linewidth = 1)
rect0 = .05, .05, .4, .9
rect1 = .5, .05, .4, .9
# This works great
ax1 = fig.add_axes(rect0)#<-----------x2TopTenSummary
ax1.hist(data, bins=13, normed=True, color='c', alpha=0.05)
#ax1.fill_between(x, pdf, where=(), alpha=.2)
ax1.fill_between(x, pdf, where=((x < thisRangeMax) & ( x > thisRangeMin)), alpha=.2, label='City Range')
ax1.vlines(dataMean, 0, stats.norm.pdf(dataMean, loc=estimated_mu, scale=estimated_sigma), color='r')
ax1.plot(x, pdf, 'k')
# This does not work :
# It just gives blue dots
ax2= fig.add_axes(rect1)
ax2= fig.add_axes(rect1)
ax2.scatter(q,z, s=200, cmap= 'Reds',norm=matplotlib.colors.Normalize(vmin=min(aColorScale) , vmax=max(aColorScale)))
# Tried to set the color map in a variety of ways:
# When kwarg 'c' is set to the variable 'aColorScale' i get the error
plt.show()
plt.close()
So my question is how do we incorporate the colormap in an application of this sort?
Multiple axes on a figure with a predetermined size (A4 or letter).
The color determination is a third variable z, (not x or y)
The color determinant is a float where 0 < z < 8
the call is ax not plt
The description of the application in the docs is unclear to me:
the doc for axes.scatter
the doc for color.normalize
I have seen plenty of examples where there is only one ax in the figure and the call is to plt.scatter... for example here
In our case x, y will be longitude, lattitude and the variable is 'data' a list or array of floats between 0 and 8.
Thanks
Okay the answer came from the PyCon Israel 2017 in this document by Tamir Lousky.
The normalization of the data and the correlation with color map happens with this block of code right here:
aColorScale = data
aColorScale = np.array(aColorScale)
norm = (aColorScale - aColorScale.min())/(aColorScale.max() - aColorScale.min())
cmap= plt.get_cmap('Reds')
colors = [cmap(tl) for tl in norm]#<---- thisRightHere
Then colors gets fed into ax2:
ax2= fig.add_axes(rect1)
ax2.scatter(q,z, s=200, color = colors)
I wish those who downvoted my question would say why, there was hours of searching and trying to find this.
Anyway here is the final image:
While I do have problems understanding the issue itself, I can tell you that the solution you have in your answer can be simplified to the usual way to plot scatters:
ax2= fig.add_axes(rect1)
ax2.scatter(q,z, c=aColorScale, s=200, cmap='Reds')
I have some code for a plot I want to create:
import numpy as np
import matplotlib.pyplot as plt
# data
X = np.linspace(-1, 3, num=50, endpoint=True)
b = 2.0
Y = X + b
# plot stuff
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(1, 1, 1)
ax.set_title('linear neuron')
# move axes
ax.spines['left'].set_position(('axes', 0.30))
# ax.spines['left'].set_smart_bounds(True)
ax.yaxis.set_ticks_position('left')
ax.spines['bottom'].set_position(('axes', 0.30))
# ax.spines['bottom'].set_smart_bounds(True)
ax.xaxis.set_ticks_position('bottom')
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
# title
title = ax.set_title('Linear Neuron', y=1.10)
# axis ticks
# ax.set_xticklabels([0 if item == 0 else '' for item in X])
# ax.set_yticklabels([])
# for tick in ax.xaxis.get_majorticklabels():
# tick.set_horizontalalignment('left')
# ax.tick_params(axis=u'both', which=u'both',length=0)
# axis labels
ax.xaxis.set_label_coords(1.04, 0.30 - 0.025)
ax.yaxis.set_label_coords(0.30 - 0.03, 1.04)
y_label = ax.set_ylabel('output')
y_label.set_rotation(0)
ax.set_xlabel('input')
# ax.get_xaxis().set_visible(False)
# ax.get_yaxis().set_visible(False)
# grid
ax.grid(True)
ax.plot(X, Y, '-', linewidth=1.5)
fig.tight_layout()
fig.savefig('plot.pdf')
In this plot the x and y axis are moved. However, the origin is not moved with then, as one can see from the ticks and ticklabels.
How can I always move the origin with the x and y axis?
I guess it would be the same as simply looking at another area of the plot, so that the x and y axis are at the lower left but not in the corner as they usually are.
To visualize this:
What I want:
Where the arrow points to the x and y axis intersection, I want to have the origin, (0|0). Where the dashed arrow points upwards I want the line to move upwards, so that it is still mathematically at the correct position, when the origin moves.
(the final result of the efforts can be found here)
You've done a lot of manual tweaking of where each thing goes, so the solution is not very portable. But here it is: remove the ax.spines['bottom'].set_position and ax.xaxis.set_label_coords calls from your original code, and add this instead:
ax.set_ylim(-1, 6)
ax.spines['bottom'].set_position('zero')
xlabel = ax.xaxis.get_label()
lpos = xlabel.get_position()
xlabel.set_position((1.04, lpos[1]))
The "bring origin up" was really accomplished by just ax.set_ylim, the rest is to get your labels where you want them.
I found a similar quesion on How to plot confusion matrix with string axis rather than integer in python. But the answer is not exact what I want. Because it doesn't contain gridding (e.g., the numbers are not in little squares) and there is background color to show the number which is not what I want.
import numpy as np
import matplotlib.pyplot as plt
conf_arr = [[33,2,0,0,0,0,0,0,0,1,3],
[3,31,0,0,0,0,0,0,0,0,0],
[0,4,41,0,0,0,0,0,0,0,1],
[0,1,0,30,0,6,0,0,0,0,1],
[0,0,0,0,38,10,0,0,0,0,0],
[0,0,0,3,1,39,0,0,0,0,4],
[0,2,2,0,4,1,31,0,0,0,2],
[0,1,0,0,0,0,0,36,0,2,0],
[0,0,0,0,0,0,1,5,37,5,1],
[3,0,0,0,0,0,0,0,0,39,0],
[0,0,0,0,0,0,0,0,0,0,38]]
norm_conf = []
for i in conf_arr:
a = 0
tmp_arr = []
a = sum(i, 0)
for j in i:
tmp_arr.append(float(j)/float(a))
norm_conf.append(tmp_arr)
fig = plt.figure()
plt.clf()
ax = fig.add_subplot(111)
ax.set_aspect(1)
res = ax.imshow(np.array(norm_conf), cmap=plt.cm.jet,
interpolation='nearest')
width, height = conf_arr.shape
for x in xrange(width):
for y in xrange(height):
ax.annotate(str(conf_arr[x][y]), xy=(y, x),
horizontalalignment='center',
verticalalignment='center')
cb = fig.colorbar(res)
alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
plt.xticks(range(width), alphabet[:width])
plt.yticks(range(height), alphabet[:height])
plt.savefig('confusion_matrix.png', format='png')
By making only a few changes to that rather excellent code proposal (I upvoted it, consider doing that too), you can get the figure you're describing.
You'll get gridding by calling the hlines and vlines methods of the ax object, which will add horizontal and vertical lines respectively.
When you then also remove the call to imshow, the colors are gone. Like this:
import numpy as np
import matplotlib.pyplot as plt
conf_arr = np.array([[33,2,0,0,0,0,0,0,0,1,3],
[3,31,0,0,0,0,0,0,0,0,0],
[0,4,41,0,0,0,0,0,0,0,1],
[0,1,0,30,0,6,0,0,0,0,1],
[0,0,0,0,38,10,0,0,0,0,0],
[0,0,0,3,1,39,0,0,0,0,4],
[0,2,2,0,4,1,31,0,0,0,2],
[0,1,0,0,0,0,0,36,0,2,0],
[0,0,0,0,0,0,1,5,37,5,1],
[3,0,0,0,0,0,0,0,0,39,0],
[0,0,0,0,0,0,0,0,0,0,38]])
height, width = conf_arr.shape
fig = plt.figure('confusion matrix')
ax = fig.add_subplot(111, aspect='equal')
for x in range(width):
for y in range(height):
ax.annotate(str(conf_arr[x][y]), xy=(y, x), ha='center', va='center')
offset = .5
ax.set_xlim(-offset, width - offset)
ax.set_ylim(-offset, height - offset)
ax.hlines(y=np.arange(height+1)- offset, xmin=-offset, xmax=width-offset)
ax.vlines(x=np.arange(width+1) - offset, ymin=-offset, ymax=height-offset)
alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
plt.xticks(range(width), alphabet[:width])
plt.yticks(range(height), alphabet[:height])
plt.savefig('confusion_matrix.png', format='png')
Remark that when you remove the call to imshow, you'll need to set the x- and y-limits explicitly, as shown above, otherwise you'll only see the lower left region (imshow updates the limits automatically depending on what you pass to it).