Plotting xscale with negative powers of 10 in matplotlib - python

Thanks in advance for taking the time to read my question. I'm having trouble plotting an array of negative powers of 10 in matplotlib
I have read through all of the log xscale options in the matplotlib documentation but to no avail. I have also read the following questions and some others which seemed similar but they did not end up helping me out:
Matplotlib: disable powers of ten in log plot
Plot Axis in Python with Log Scale for Negative Exponents of 10
Logscale plots with zero values in matplotlib *with negative exponents*
Logscale plots with zero values in matplotlib
This is more or less in simple form what my code looks like.
x = np array with 100 values of 0.99999, 0.9999, 0.999, 0.99
y = np array with corresponding evaluation outputs to be plotted.
plt.plot(x, y, 'o')
plt.xticks([0.99999,0.9999,0.999,0.99])
plt.gca().set_xscale('log')
What I'm trying to attain is a plot with just the values 0.99999, 0.9999, 0.999, 0.99 evenly spaced on the x axis plotted against their corresponding y values.
Once again I would like to thank you so much for taking the time to read this question, and I sincerely hope you can help me out!

You could easily "fake" this kind of plot, by plotting y against [0,1,2,3,...] and then replacing the xtickslabel with [0.9999,0.999,0.99,...]
x = [0.99999,0.9999,0.999,0.99]
y = [1,2,3,4]
fig, ax = plt.subplots()
ax.plot(range(len(x)),y, 'o-')
ax.set_xticks(range(len(x)))
ax.set_xticklabels(x)

Related

Set log xticks in matplotlib for a linear plot

Consider
xdata=np.random.normal(5e5,2e5,int(1e4))
plt.hist(np.log10(xdata), bins=100)
plt.show()
plt.semilogy(xdata)
plt.show()
is there any way to display xticks of the first plot (plt.hist) as in the second plot's yticks? For good reasons I want to histogram the np.log10(xdata) of xdata but I'd like to set minor ticks to display as usual in a log scale (even considering that the exponent is linear...)
In other words, I want the x_axis of this plot:
to be like the y_axis
of the 2nd plot, without changing the spacing between major ticks (e.g., adding log marks between 5.5 and 6.0, without altering these values)
Proper histogram plot with logarithmic x-axis:
Explanation:
Cut off negative values
The randomly generated example data likely contains still some negative values
activate the commented code lines at the beginning to see the effect
logarithmic function isn't defined for values <= 0
while the 2nd plot just deals with y-axis log scaling (negative values are just out of range), the 1st plot doesn't work with negative values in the BINs range
probably real world working data won't be <= 0, otherwise keep that in mind
BINs should be aligned to log scale as well
otherwise the 'BINs widths' distribution looks off
switch # on the plt.hist( statements in the 1st plot section to see the effect)
xdata (not np.log10(xdata)) to be plotted in the histogram
that 'workaround' with plotting np.log10(xdata) probably was the root cause for the misunderstanding in the comments
Code:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(42) # just to have repeatable results for the answer
xdata=np.random.normal(5e5,2e5,int(1e4))
# MIN_xdata, MAX_xdata = np.min(xdata), np.max(xdata)
# print(f"{MIN_xdata}, {MAX_xdata}") # note the negative values
# cut off potential negative values (log function isn't defined for <= 0 )
xdata = np.ma.masked_less_equal(xdata, 0)
MIN_xdata, MAX_xdata = np.min(xdata), np.max(xdata)
# print(f"{MIN_xdata}, {MAX_xdata}")
# align the bins to fit a log scale
bins = 100
bins_log_aligned = np.logspace(np.log10(MIN_xdata), np.log10(MAX_xdata), bins)
# 1st plot
plt.hist(xdata, bins = bins_log_aligned) # note: xdata (not np.log10(xdata) )
# plt.hist(xdata, bins = 100)
plt.xscale('log')
plt.show()
# 2nd plot
plt.semilogy(xdata)
plt.show()
Just kept for now for clarification purpose. Will be deleted when the question is revised.
Disclaimer:
As Lucas M. Uriarte already mentioned that isn't an expected way of changing axis ticks.
x axis ticks and labels don't represent the plotted data
You should at least always provide that information along with such a plot.
The plot
From seeing the result I kinda understand where that special plot idea is coming from - still there should be a preferred way (e.g. conversion of the data in advance) to do such a plot instead of 'faking' the axis.
Explanation how that special axis transfer plot is done:
original x-axis is hidden
a twiny axis is added
note that its y-axis is hidden by default, so that doesn't need handling
twiny x-axis is set to log and the 2nd plot y-axis limits are transferred
subplots used to directly transfer the 2nd plot y-axis limits
use variables if you need to stick with your two plots
twiny x-axis is moved from top (twiny default position) to bottom (where the original x-axis was)
Code:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(42) # just to have repeatable results for the answer
xdata=np.random.normal(5e5,2e5,int(1e4))
plt.figure()
fig, axs = plt.subplots(2, figsize=(7,10), facecolor=(1, 1, 1))
# 1st plot
axs[0].hist(np.log10(xdata), bins=100) # plot the data on the normal x axis
axs[0].axes.xaxis.set_visible(False) # hide the normal x axis
# 2nd plot
axs[1].semilogy(xdata)
# 1st plot - twin axis
axs0_y_twin = axs[0].twiny() # set a twiny axis, note twiny y axis is hidden by default
axs0_y_twin.set(xscale="log")
# transfer the limits from the 2nd plot y axis to the twin axis
axs0_y_twin.set_xlim(axs[1].get_ylim()[0],
axs[1].get_ylim()[1])
# move the twin x axis from top to bottom
axs0_y_twin.tick_params(axis="x", which="both", bottom=True, top=False,
labelbottom=True, labeltop=False)
# Disclaimer
disclaimer_text = "Disclaimer: x axis ticks and labels don't represent the plotted data"
axs[0].text(0.5,-0.09, disclaimer_text, size=12, ha="center", color="red",
transform=axs[0].transAxes)
plt.tight_layout()
plt.subplots_adjust(hspace=0.2)
plt.show()

Matplotlib - Plot content vanishes using plt.yscale('log') [duplicate]

I am currently using logscale in order to have greater possibilities of plotting my data. Nevertheless, my data consists also of zero values. I know that these zero values will not work on logscale as log(0) is not defined.
So e.g.,
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot([0,1,2],[10,10,100],marker='o',linestyle='-')
ax.set_yscale('log')
ax.set_xscale('log')
completely omits the zero value. Is this behavior acceptable? At least there should be some kind of warning. I only recognized it by accident. Is there maybe also a way of plotting zero value data in logscale?
Thanks!
P.S.: I hope this fits to stackoverflow. I did not find a mailing list of matplotlib.
It's easiest to use a "symlog" plot for this purpose. The interval near 0 will be on a linear scale, so 0 can be displayed.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot([0,1,2],[10,10,100],marker='o',linestyle='-')
ax.set_yscale('symlog')
ax.set_xscale('symlog')
plt.show()
Symlog sets a small interval near zero (both above and below) to use a linear scale. This allows things to cross 0 without causing log(x) to explode (or go to -inf, rather).
There's a nice visual comparison as an SO answer here: https://stackoverflow.com/a/3513150/325565

Scale colormap for contour and contourf

I'm trying to plot the contour map of a given function f(x,y), but since the functions output scales really fast, I'm losing a lot of information for lower values of x and y. I found on the forums to work that out using vmax=vmax, it actually worked, but only when plotted for a specific limit of x and y and levels of the colormap.
Say I have this plot:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
u = np.linspace(-2,2,1000)
x,y = np.meshgrid(u,u)
z = (1-x)**2+100*(y-x**2)**2
cont = plt.contour(x,y,z,500,colors='black',linewidths=.3)
cont = plt.contourf(x,y,z,500,cmap="jet",vmax=100)
plt.colorbar(cont)
plt.show
I want to uncover whats beyond the axis limits keeping the same scale, but if I change de x and y limits to -3 and 3 I get:
See how I lost most of my levels since my max value for the function at these limits are much higher. A work around to this problem is to increase the levels to 1000, but that takes a lot of computational time.
Is there a way to plot only the contour levels that I need? That is, between 0 and 100.
An example of a desired output would be:
With the white space being the continuation of the plot without resizing the levels.
The code I'm using is the one given after the first image.
There are a few possible ideas here. The one I very much prefer is a logarithmic representation of the data. An example would be
from matplotlib import ticker
fig = plt.figure(1)
cont1 = plt.contourf(x,y,z,cmap="jet",locator=ticker.LogLocator(numticks=10))
plt.colorbar(cont1)
plt.show()
fig = plt.figure(2)
cont2 = plt.contourf(x,y,np.log10(z),100,cmap="jet")
plt.colorbar(cont2)
plt.show()
The first example uses matplotlibs LogLocator functions. The second one just directly computes the logarithm of the data and plots that normally.
The third example just caps all data above 100.
fig = plt.figure(3)
zcapped = z.copy()
zcapped[zcapped>100]=100
cont3 = plt.contourf(x,y,zcapped,100,cmap="jet")
cbar = plt.colorbar(cont3)
plt.show()

Matplotlib - Boxplot calculated on log10 values but shown in logarithmic scale

I think this is a simple question, but I just still can't seem to think of a simple solution. I have a set of data of molecular abundances, with values ranging many orders of magnitude. I want to represent these abundances with boxplots (box-and-whiskers plots), and I want the boxes to be calculated on log scale because of the wide range of values.
I know I can just calculate the log10 of the data and send it to matplotlib's boxplot, but this does not retain the logarithmic scale in plots later.
So my question is basically this:
When I have calculated a boxplot based on the log10 of my values, how do I convert the plot afterward to be shown on a logarithmic scale instead of linear with the log10 values?
I can change tick labels to partly fix this, but I have no clue how I get logarithmic scales back to the plot.
Or is there another more direct way to plotting this. A different package maybe that has this options already included?
Many thanks for the help.
I'd advice against doing the boxplot on the raw values and setting the y-axis to logarithmic, because the boxplot function is not designed to work across orders of magnitudes and you may get too many outliers (depends on your data, of course).
Instead, you can plot the logarithm of the data and manually adjust the y-labels.
Here is a very crude example:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator, FormatStrFormatter
np.random.seed(42)
values = 10 ** np.random.uniform(-3, 3, size=100)
fig = plt.figure(figsize=(9, 3))
ax = plt.subplot(1, 3, 1)
ax.boxplot(np.log10(values))
ax.set_yticks(np.arange(-3, 4))
ax.set_yticklabels(10.0**np.arange(-3, 4))
ax.set_title('log')
ax = plt.subplot(1, 3, 2)
ax.boxplot(values)
ax.set_yscale('log')
ax.set_title('raw')
ax = plt.subplot(1, 3, 3)
ax.boxplot(values, whis=[5, 95])
ax.set_yscale('log')
ax.set_title('5%')
plt.show()
The right figure shows the box plot on the raw values. This leads to many outliers, because the maximum whisker length is computed as a multiple (default: 1.5) of the interquartile range (the box height), which does not scale across orders of magnitude.
Alternatively, you could specify to draw the whiskers for a given percentile range:
ax.boxplot(values, whis=[5, 95])
In this case you get a fixed amount of outlires (5%) above and below.
You can use plt.yscale:
plt.boxplot(data); plt.yscale('log')

Creating two x-axes for a line-plot in matplotlib with unknown transform function between scales

Using matplotlib, two x-axes for 1 line plot can easily be obtained using twiny().
If the transform between the two x-scales can be described by a function, the corresponding ticks can be set by applying this transform function.
(this is described here: How to add a second x-axis in matplotlib)
How can I achieve this, if the transform function between the scales is unknown?
Edit:
Imagine the following situation:
You have 2 thermometers, both measuring the temperature. Thermometer 1 is measuring in °C and thermometer 2 in an imaginary unit, lets call it °D. Basically, what you know is that with increasing °C °D is increasing as well. Additionally, both thermometers have some degree of inaccuracy.
Both thermometers measure the same physical quantity, hence I should be able to represent them with a single line and two scales. However, in contrast to plotting tempoeratures in °C vs. K or °F, the transformation between the scales is unknown.
This means for example I have:
import numpy as np
from matplotlib import pyplot as plt
temp1 = np.sort(np.random.uniform(size=21))
temp2 = np.sort(np.random.uniform(low=-20, high=20, size=21))
y = np.linspace(0,1,21, endpoint=True)
A transform function between temp1 and temp2 is existent, but unknow. Y, however, is the same.
Additionally, I know that temp1 and y are confined to the range (0,1)
Now we may plot like this:
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.set_aspect('equal')
ax2 = plt.twiny(ax1)
ax1.plot(x1,y, 'k-')
ax2.plot(x2,y, 'r:')
ax1.set_xlabel(r'1st x-axis')
ax2.set_xlabel(r'2nd x-axis')
ax1.set_xlim([0,1])
ax1.set_ylim([0,1])
fig.savefig('dual_x_faulty.png', format='png')
This leads to the following plot:
You can see that both curves are not the same, and the plot is not square (as it would be without twinning the y axis).
So, here is what I want (and can't achieve on my own):
Plotting a 3d-array (temp1, temp2, y) in a 2d line plot by having two x-axes
Matplotlib shoud 'automagically' set the ticks of temp2 such, that the curves (temp1, y) and (temp2, y) are congruent
Is there a workaround?
Thanks for your help!

Categories

Resources