Hello Python/Matplotlib gurus,
I would like to label the y-axis at a random point where a particular horizontal line is drawn.
My Y-axis should not have any values, and only show major ticks.
To illustrate my request clearly, I will use some screenshots.
What I have currently:
What I want:
As you can see, E1 and E2 are not exactly at the major tick mark. Actually, I know the y-axis values (although they should be hidden, since it's a model graph). I also know the values of E1 and E2.
I would appreciate some help.
Let my code snippet be as follows:
ax3.axis([0,800,0,2500) #You can see that the major YTick-marks will be at 500 intervals
ax3.plot(x,y) #plot my lines
E1 = 1447
E2 = 2456
all_ticks = ax3.yaxis.get_all_ticks() #method that does not exist. If it did, I would be able to bind labels E1 and E2 to the respective values.
Thank you for the help!
Edit:
For another graph, I use this code to have various colors for the labels. This works nicely. energy_range, labels_energy, colors_energy are numpy arrays as large as my y-axis, in my case, 2500.
#Modify the labels and colors of the Power y-axis
for i, y in enumerate(energy_range):
if (i == int(math.floor(E1))):
labels_energy[i] = '$E_1$'
colors_energy[i] = 'blue'
elif (i == int(math.floor(E2))):
labels_energy[i] = '$E_2$'
colors_energy[i] = 'green'
else:
labels_energy.append('')
#Modify the colour of the energy y-axis ticks
for color,tick in zip(colors_energy,ax3.yaxis.get_major_ticks()):
print color, tick
if color:
print color
tick.label1.set_color(color) #set the color property
ax3.get_yaxis().set_ticklabels(labels_energy)
Edit2:
Full sample with dummy values:
#!/bin/python
import matplotlib
# matplotlib.use('Agg') #Remote, block show()
import numpy as np
import pylab as pylab
from pylab import *
import math
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator
import matplotlib.font_manager as fm
from matplotlib.font_manager import FontProperties
import matplotlib.dates as mdates
from datetime import datetime
import matplotlib.cm as cm
from matplotlib.ticker import MultipleLocator, FormatStrFormatter
from scipy import interpolate
def plot_sketch():
x = np.arange(0,800,1)
energy_range = range (0,2500,1) #Power graph y-axis range
labels_energy = [''] * len(energy_range)
colors_energy = [''] * len(energy_range)
f1=4
P1=3
P2=2
P3=4
f2=2
f3=6
#Set Axes ranges
ax3.axis([0,800,0,energy_range[-1]])
#Add Energy lines; E=integral(P) dt
y=[i * P1 for i in x]
ax3.plot(x,y, color='b')
y = [i * P2 for i in x[:0.3*800]]
ax3.plot(x[:0.3*800],y, color='g')
last_val = y[-1]
y = [(i * P3 -last_val) for i in x[(0.3*800):(0.6*800)]]
ax3.plot(x[(0.3*800):(0.6*800)],y, color='g')
E1 = x[-1] * P1
E2 = (0.3 * x[-1]) * P2 + x[-1] * (0.6-0.3) * P3
#Modify the labels and colors of the Power y-axis
for i, y in enumerate(energy_range):
if (i == int(math.floor(E1))):
labels_energy[i] = '$E_1$'
colors_energy[i] = 'blue'
elif (i == int(math.floor(E2))):
labels_energy[i] = '$E_2$'
colors_energy[i] = 'green'
else:
labels_energy.append('')
#Modify the colour of the power y-axis ticks
for color,tick in zip(colors_energy,ax3.yaxis.get_major_ticks()):
if color:
tick.label1.set_color(color) #set the color property
ax3.get_yaxis().set_ticklabels(labels_energy)
ax3.axhline(energy_range[int(math.floor(E1))], xmin=0, xmax=1, linewidth=0.25, color='b', linestyle='--')
ax3.axhline(energy_range[int(math.floor(E2))], xmin=0, xmax=0.6, linewidth=0.25, color='g', linestyle='--')
#Show grid
ax3.xaxis.grid(True)
#fig = Sketch graph
fig = plt.figure(num=None, figsize=(14, 7), dpi=80, facecolor='w', edgecolor='k')
fig.canvas.set_window_title('Sketch graph')
ax3 = fig.add_subplot(111) #Energy plot
ax3.set_xlabel('Time (ms)', fontsize=12)
ax3.set_ylabel('Energy (J)', fontsize=12)
pylab.xlim(xmin=0) # start at 0
plot_sketch()
plt.subplots_adjust(hspace=0)
plt.show()
I think you're looking for the correct transform (check this out). In your case, what I think you want is to simply use the text method with the correct transform kwarg. Try adding this to your plot_sketch function after your axhline calls:
ax3.text(0, energy_range[int(math.floor(E1))],
'E1', color='g',
ha='right',
va='center',
transform=ax3.get_yaxis_transform(),
)
ax3.text(0, energy_range[int(math.floor(E2))],
'E2', color='b',
ha='right',
va='center',
transform=ax3.get_yaxis_transform(),
)
The get_yaxis_transform method returns a 'blended' transform which makes the x values input to the text call be plotted in axes units, and the y data in 'data' units. You can adjust the value of the x-data, (0) to be -0.003 or something if you want a little padding (or you could use a ScaledTranslation transform, but that's generally unnecessary if this is a one-off fix).
You'll probably also want to use the 'labelpad' option for set_ylabel, e.g.:
ax3.set_ylabel('Energy (J)', fontsize=12, labelpad=20)
I think my answer to a different post might be of help to you:
Matplotlib: Add strings as custom x-ticks but also keep existing (numeric) tick labels? Alternatives to matplotlib.pyplot.annotate?
It also works for the y-axis.Here is the result:
Related
I have plotted a wedge-diagram, but can not seem to figure out how to get labels on the axis and units on the values. The r-axis has to be Dec and the theta-axis has to be RA, both with units of degrees.
Here is my code, hope you can help:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
theta = (np.pi/180)*np.array([340.555906,3.592373,32.473440,33.171584,35.463857,44.268397,339.362504,345.211906,346.485567,346.811945,348.672405,349.180736,349.370850,353.098343])
r = np.array([-32.906663,-33.842402,-32.425917,-32.677975, -30.701083,-31.460307,-32.909861,-30.802969,-33.683759,-32.207783,-33.068686,-33.820102,-31.438195,-31.920375])
colors = 'black'
fig = plt.figure()
ax = fig.add_subplot(111, polar=True)
c = ax.scatter(theta, r,marker='.', c=colors, cmap='hsv', alpha=0.75)
ax.set_thetamin(55)
ax.set_thetamax(-45)
fmt = lambda x, pos: "{:g}".format(np.degrees(x if x >= 0 else x + 2 * np.pi))
ax.xaxis.set_major_formatter(ticker.FuncFormatter(fmt))
plt.show()
EDIT: I have tried setting the lables with:
ax.text(0,-29.9,s='RA')
ax.text(2,-29.5,s='Dec')
Which sort of works, but I can still not get units (degrees) on my values. I think the problem might have something to do with my limits, which has to go around zero, but I am not sure.
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:
I am plotting from a CSV file that contains Cartesian coordinates and I want to change it to Polar coordinates, then plot using the Polar coordinates.
Here is the code
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import seaborn as sns
df = pd.read_csv('test_for_plotting.csv',index_col = 0)
x_temp = df['x'].values
y_temp = df['y'].values
df['radius'] = np.sqrt( np.power(x_temp,2) + np.power(y_temp,2) )
df['theta'] = np.arctan2(y_temp,x_temp)
df['degrees'] = np.degrees(df['theta'].values)
df['radians'] = np.radians(df['degrees'].values)
ax = plt.axes(polar = True)
ax.set_aspect('equal')
ax.axis("off")
sns.set(rc={'axes.facecolor':'white', 'figure.facecolor':'white','figure.figsize':(10,10)})
# sns.scatterplot(data = df, x = 'x',y = 'y', s= 1,alpha = 0.1, color = 'black',ax = ax)
sns.scatterplot(data = df, x = 'radians',y = 'radius', s= 1,alpha = 0.1, color = 'black',ax = ax)
plt.tight_layout()
plt.show()
Here is the dataset
If you run this command using polar = False and use this line to plot sns.scatterplot(data = df, x = 'x',y = 'y', s= 1,alpha = 0.1, color = 'black',ax = ax) it will result in this picture
now after setting polar = True and run this line to plot sns.scatterplot(data = df, x = 'radians',y = 'radius', s= 1,alpha = 0.1, color = 'black',ax = ax) It is supposed to give you this
But it is not working as if you run the actual code the shape in the Polar format is the same as Cartesian which does not make sense and it does not match the picture I showed you for polar (If you are wondering where did I get the second picture from, I plotted it using R)
I would appreciate your help and insights and thanks in advance!
For a polar plot, the "x-axis" represents the angle in radians. So, you need to switch x and y, and convert the angles to radians (I also added ax=ax, as the axes was created explicitly):
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import seaborn as sns
data = {'radius': [0, 0.5, 1, 1.5, 2, 2.5], 'degrees': [0, 25, 75, 155, 245, 335]}
df_temp = pd.DataFrame(data)
ax = plt.axes(polar=True)
sns.scatterplot(x=np.radians(df_temp['degrees']), y=df_temp['radius'].to_numpy(),
s=100, alpha=1, color='black', ax=ax)
for deg, y in zip(df_temp['degrees'], df_temp['radius']):
x = np.radians(deg)
ax.axvline(x, color='skyblue', ls=':')
ax.text(x, y, f' {deg}', color='crimson')
ax.set_rlabel_position(-15) # Move radial labels away from plotted dots
plt.tight_layout()
plt.show()
About your new question: if you have an xy plot, and you convert these xy values to polar coordinates, and then plot these on a polar plot, you'll get again the same plot.
After some more testing with the data, I decided to create the plot directly with matplotlib, as seaborn makes some changes that don't have exactly equal effects across seaborn and matplotlib versions.
What seems to be happening in R:
The angles (given by "x") are spread out to fill the range (0,2 pi). This either requires a rescaling of x, or change how the x-values are mapped to angles. One way to get this, is subtracting the minimum. And with that result divide by the new maximum and multiply by 2 pi.
The 0 of the angles it at the top, and the angles go clockwise.
The following code should create the plot with Python. You might want to experiment with alpha and with s in the scatter plot options. (Default the scatter dots get an outline, which often isn't desired when working with very small dots, and can be removed by lw=0.)
ax = plt.axes(polar=True)
ax.set_aspect('equal')
ax.axis('off')
x_temp = df['x'].to_numpy()
y_temp = df['y'].to_numpy()
x_temp -= x_temp.min()
x_temp = x_temp / x_temp.max() * 2 * np.pi
ax.scatter(x=x_temp, y=y_temp, s=0.05, alpha=1, color='black', lw=0)
ax.set_rlim(y_temp.min(), y_temp.max())
ax.set_theta_zero_location("N") # set zero at the north (top)
ax.set_theta_direction(-1) # go clockwise
plt.show()
At the left the resulting image, at the right using the y-values for coloring (ax.scatter(..., c=y_temp, s=0.05, alpha=1, cmap='plasma_r', lw=0)):
I have a seaborn heatmap that looks like this:
...generated from a pandas dataframe of randomly generated values a piece of which looks like this:
The values along the y axis are all in the range [0,1], and the ones on the x axis in the range [0,2*pi], and I just want some short floats at regular intervals for my tick labels, but I can only seem to get values that are in my dataframe. When I try specifying the values I want, it doesn't put them in the right place, as seen in the plot above. He's my code right now. How can I get the axis labels that I tried specifying with xticks and yticks in this code in the correct places (which would be evenly spaced along the axes)?
import pandas as pd
import numpy as np
import matplotlib as plt
from matplotlib.mlab import griddata
sns.set_style("darkgrid")
PHI, COSTH = np.meshgrid(phis, cos_thetas)
THICK = griddata(phis, cos_thetas, thicknesses, PHI, COSTH, interp='linear')
thick_df = pd.DataFrame(THICK, columns=phis, index=cos_thetas)
thick_df = thick_df.sort_index(axis=0, ascending=False)
thick_df = thick_df.sort_index(axis=1)
cmap = sns.cubehelix_palette(start=1.6, light=0.8, as_cmap=True, reverse=True)
yticks = np.array([0,0.2,0.4,0.6,0.8,1.0])
xticks = np.array([0,1,2,3,4,5,6])
g = sns.heatmap(thick_df, linewidth=0, xticklabels=xticks, yticklabels=yticks, square=True, cmap=cmap)
plt.show(g)
Here's something that should do what you want:
cmap = sns.cubehelix_palette(start=1.6, light=0.8, as_cmap=True, reverse=True)
yticks = np.linspace(0,1,6)
x_end = 6
xticks = np.arange(x_end+1)
ax = sns.heatmap(thick_df, linewidth=0, xticklabels=xticks, yticklabels=yticks[::-1], square=True, cmap=cmap)
ax.set_xticks(xticks*ax.get_xlim()[1]/(2*math.pi))
ax.set_yticks(yticks*ax.get_ylim()[1])
plt.show()
You could pass ['{:,.2f}'.format(x) for x in xticks] instead of xticks to get a float with 2 decimals.
Note that I'm reversing the yticklabels because that's what seaborn does: see matrix.py#L138.
Seaborn calculates the tick positions around the same place (e.g.: #L148), for you that amounts to:
# thick_df.T.shape[0] = thick_df.shape[1]
xticks: np.arange(0, thick_df.T.shape[0], 1) + .5
yticks: np.arange(0, thick_df.T.shape[1], 1) + .5
I'm currently using Matplotlib to create a histogram:
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as pyplot
...
fig = pyplot.figure()
ax = fig.add_subplot(1,1,1,)
n, bins, patches = ax.hist(measurements, bins=50, range=(graph_minimum, graph_maximum), histtype='bar')
#ax.set_xticklabels([n], rotation='vertical')
for patch in patches:
patch.set_facecolor('r')
pyplot.title('Spam and Ham')
pyplot.xlabel('Time (in seconds)')
pyplot.ylabel('Bits of Ham')
pyplot.savefig(output_filename)
I'd like to make the x-axis labels a bit more meaningful.
Firstly, the x-axis ticks here seem to be limited to five ticks. No matter what I do, I can't seem to change this - even if I add more xticklabels, it only uses the first five. I'm not sure how Matplotlib calculates this, but I assume it's auto-calculated from the range/data?
Is there some way I can increase the resolution of x-tick labels - even to the point of one for each bar/bin?
(Ideally, I'd also like the seconds to be reformatted in micro-seconds/milli-seconds, but that's a question for another day).
Secondly, I'd like each individual bar labeled - with the actual number in that bin, as well as the percentage of the total of all bins.
The final output might look something like this:
Is something like that possible with Matplotlib?
Cheers,
Victor
Sure! To set the ticks, just, well... Set the ticks (see matplotlib.pyplot.xticks or ax.set_xticks). (Also, you don't need to manually set the facecolor of the patches. You can just pass in a keyword argument.)
For the rest, you'll need to do some slightly more fancy things with the labeling, but matplotlib makes it fairly easy.
As an example:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import FormatStrFormatter
data = np.random.randn(82)
fig, ax = plt.subplots()
counts, bins, patches = ax.hist(data, facecolor='yellow', edgecolor='gray')
# Set the ticks to be at the edges of the bins.
ax.set_xticks(bins)
# Set the xaxis's tick labels to be formatted with 1 decimal place...
ax.xaxis.set_major_formatter(FormatStrFormatter('%0.1f'))
# Change the colors of bars at the edges...
twentyfifth, seventyfifth = np.percentile(data, [25, 75])
for patch, rightside, leftside in zip(patches, bins[1:], bins[:-1]):
if rightside < twentyfifth:
patch.set_facecolor('green')
elif leftside > seventyfifth:
patch.set_facecolor('red')
# Label the raw counts and the percentages below the x-axis...
bin_centers = 0.5 * np.diff(bins) + bins[:-1]
for count, x in zip(counts, bin_centers):
# Label the raw counts
ax.annotate(str(count), xy=(x, 0), xycoords=('data', 'axes fraction'),
xytext=(0, -18), textcoords='offset points', va='top', ha='center')
# Label the percentages
percent = '%0.0f%%' % (100 * float(count) / counts.sum())
ax.annotate(percent, xy=(x, 0), xycoords=('data', 'axes fraction'),
xytext=(0, -32), textcoords='offset points', va='top', ha='center')
# Give ourselves some more room at the bottom of the plot
plt.subplots_adjust(bottom=0.15)
plt.show()
One thing I wanted to add to the plots in the histogram with "density = True" was the relative frequency values for each bin, search but I couldn't find a function that would do that. A solution I made follows as image:
The function:
def label_densityHist(ax, n, bins, x=4, y=0.01, r=2, **kwargs):
"""
Add labels,relative value of bin, to each bin in a density histogram .
:param ax: Object axe of matplotlib
The axis to plot.
:param n: list, array of int, float
The values of the histogram bins.
:param bins: list, array of int, float
The edges of the bins.
:param x: int, float
Related the x position of the bin labels. The higher, the lower the value on the x-axis.
Default: 4
:param y: int, float
Related the y position of the bin labels. The higher, the greater the value on the y-axis.
Default: 0.01
:param r: int
Number of decimal places.
Default: 2
:param **kwargs: Text properties in matplotlib
:return: None
Example
import matplotlib.pyplot as plt
import numpy as np
dados = np.random.randn(100)
axe = plt.gca()
n, bins, _ = axe.hist(x=dados, edgecolor='black')
label_densityHist(axe,n, bins)
plt.show()
Example:
import matplotlib.pyplot as plt
import numpy as np
dados = np.random.randn(100)
axe = plt.gca()
n, bins, _ = axe.hist(x=dados, edgecolor='black')
label_densityHist(axe,n, bins, x=6, fontsize='large')
plt.show()
Reference:
[1]https://matplotlib.org/3.1.1/api/text_api.html#matplotlib.text.Text
"""
k = []
# calculate the relative frequency of each bin
for i in range(0,len(n)):
k.append((bins[i+1]-bins[i])*n[i])
# rounded
k = around(k,r); #print(k)
# plot the label/text to each bin
for i in range(0, len(n)):
x_pos = (bins[i + 1] - bins[i]) / x + bins[i]
y_pos = n[i] + (n[i] * y)
label = str(k[i]) # relative frequency of each bin
ax.text(x_pos, y_pos, label, kwargs)
To add SI prefixes to your axis labels you want to use QuantiPhy. In fact, in its documentation it has an example that shows how to do this exact thing: MatPlotLib Example.
I think you would add something like this to your code:
from matplotlib.ticker import FuncFormatter
from quantiphy import Quantity
time_fmtr = FuncFormatter(lambda v, p: Quantity(v, 's').render(prec=2))
ax.xaxis.set_major_formatter(time_fmtr)