Set log xticks in matplotlib for a linear plot

Viewed 57

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:

enter image description here

to be like the y_axis

enter image description here

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)

3 Answers

yes you need to set the axes as log

ax = plt.gca()  # get current axes
ax.set_yscale("log")

EDIT

Please note that plt module has not method set_yscale, if you don't want to recover the axes and alternative can be:

fig, ax = plt.subplots()
plt.hist(xdata) # equivalent to ax.hist(xdata)
ax.set_yscale("log")
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.

enter image description here

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()

Proper histogram plot with logarithmic x-axis:

enter image description here

Explanation:

  1. 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
  2. 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)
  3. 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()
Related