How to add one custom tickline in matplotlib axes - python

Is there a programmatic way to force the appearance of a single tickline at the additional tick location shown below?
Requirements:
Tickline should be pointing down from x-axis
Tickline should extend to label 103 regardless of padding
Tickline should be the same color and thickness of axes
No changes to other ticks (ticklines or tick labels)
Code and sample image:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
fig, ax = plt.subplots(figsize=(6, 8 / 3))
fig.tight_layout()
x_limit = 103
x = [0, 10, 50, 100, x_limit]
y = [6, 2, 5, 2, 20]
ax.plot(x, y)
# Add a tick which represents the maximum x-value
xticks = ax.xaxis.get_majorticklocs()
ax.xaxis.set_ticks(np.append(xticks, x_limit))
# Change padding of tick in the event other ticks get too close
tick = ax.get_xaxis().get_major_ticks()[-1]
tick.set_pad(14)
tick.label1 = tick._get_text1()
# Set tight axes bounds around data
ax.set_ylim(0, max(y) + 1)
ax.set_xlim(0, x_limit)
EDIT: Tried tcaswell's solution and ended up with an annotation in the right place. I do notice some aliasing as if it doesn't look like an extension of the y-axis. Any ideas on how to clean this up?

You can do this with annotate and a bit of transform magic.
import matplotlib.transforms as mtransforms
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(9, 3), tight_layout=True)
ax.set_xlim(90, 105)
trans = mtransforms.blended_transform_factory(ax.transData, ax.transAxes)
tt = ax.annotate('103', (103, 0), xytext=(0, -12), transform=trans,
arrowprops={'arrowstyle': '-'}, ha='center', va='top',
textcoords='offset points')

Related

Axis labels in line with tick labels in matplotlib

For space reasons, I sometimes make plots in the following style:
fig, ax = plt.subplots(figsize=(3, 3))
ax.plot([0,1], [1000, 1001])
ax.set_xticks([0, 1])
ax.set_yticks([1000, 1001])
ax.set_xlabel("x", labelpad=-8)
ax.set_ylabel("y", labelpad=-18)
Here, I've kept just ticks marking the boundaries of the X/Y domains, and I'm manually aligning the xlabel and ylabel using the labelpad keyword argument so that the x and y axis labels visually align with the tick labels.
Note, I've had to use different amounts of padding for the different axes, since the length of the y tick labels 1000 and 1001 extends farther away from the axis than the height of the x tick labels 0 and 1, and since the vertical position of the x axis label and the horizontal position of the y axis label are relative to their usual position, which would be just past the extent of the tick labels.
I'm wondering, is there a way to automate this procedure, and to do it exactly rather than visually? For example, if labelpad were relative to the spines, that would be very nice, or if there were a way to determine the extent of the ticks and tick labels away from the spines, that number could be used to automate this as well.
A similar effect can be obtained using ax.yaxis.set_label_coords, but this transforms the position relative to the axes' transform, and thus depends on the size of the axes, while the ticks are positioned absolutely relative to the spines.
The path you were going down with ax.{x,y}axis.set_label_coords was pretty much there! All you need to do is wrap the transAxes transform in an offset_copy and then provide an offset that is a combination of the current length of the ticks + any space around the tick bbox.
Using Transforms
import matplotlib.pyplot as plt
from matplotlib.transforms import offset_copy
fig, ax = plt.subplots(figsize=(3, 3))
fig.set_facecolor('white')
ax.plot([0,1], [1000, 1001])
ax.set_xticks([0, 1])
ax.set_yticks([1000, 1001])
# Create a transform that vertically offsets the label
# starting at the edge of the Axes and moving downwards
# according to the total length of the bounding box of a major tick
t = offset_copy(
ax.transAxes, y=-(ax.xaxis.get_tick_padding() + ax.xaxis.majorTicks[0].get_pad()),
fig=fig, units='dots'
)
ax.xaxis.set_label_coords(.5, 0, transform=t)
ax.set_xlabel('x', va='top')
# Repeat the above, but on the y-axis
t = offset_copy(
ax.transAxes, x=-(ax.yaxis.get_tick_padding() + ax.yaxis.majorTicks[0].get_pad()),
fig=fig, units='dots'
)
ax.yaxis.set_label_coords(0, .5, transform=t)
ax.set_ylabel('y', va='bottom')
Test with longer ticks
import matplotlib.pyplot as plt
from matplotlib.transforms import offset_copy
fig, ax = plt.subplots(figsize=(3, 3))
fig.set_facecolor('white')
ax.plot([0,1], [1000, 1001])
ax.set_xticks([0, 1])
ax.set_yticks([1000, 1001])
ax.xaxis.set_tick_params(length=10)
ax.yaxis.set_tick_params(length=15)
t = offset_copy(
ax.transAxes, y=-(ax.xaxis.get_tick_padding() + ax.xaxis.majorTicks[0].get_pad()),
fig=fig, units='points'
)
ax.xaxis.set_label_coords(.5, 0, transform=t)
ax.set_xlabel('x', va='top')
t = offset_copy(
ax.transAxes, x=-(ax.yaxis.get_tick_padding() + ax.yaxis.majorTicks[0].get_pad()),
fig=fig, units='points'
)
ax.yaxis.set_label_coords(0, .5, transform=t)
ax.set_ylabel('y', va='bottom')
Longer ticks & increased DPI
import matplotlib.pyplot as plt
from matplotlib.transforms import offset_copy
fig, ax = plt.subplots(figsize=(3, 3), dpi=150)
fig.set_facecolor('white')
ax.plot([0,1], [1000, 1001])
ax.set_xticks([0, 1])
ax.set_yticks([1000, 1001])
ax.xaxis.set_tick_params(length=10)
ax.yaxis.set_tick_params(length=15)
t = offset_copy(
ax.transAxes, y=-(ax.xaxis.get_tick_padding() + ax.xaxis.majorTicks[0].get_pad()),
fig=fig, units='points'
)
ax.xaxis.set_label_coords(.5, 0, transform=t)
ax.set_xlabel('x', va='top')
t = offset_copy(
ax.transAxes, x=-(ax.yaxis.get_tick_padding() + ax.yaxis.majorTicks[0].get_pad()),
fig=fig, units='points'
)
ax.yaxis.set_label_coords(0, .5, transform=t)
ax.set_ylabel("y", va='bottom')

Difference between plotting a graph with/without axes with/without the same axes name inside a subplot

What is the difference between plotting a graph with/without axes with/without the same name inside a subplot? They all output the same graph.
Plotting a graph with axes with the same name inside a subplot:
from matplotlib import pyplot as plt
plt.figure(figsize=(10,5))
ax = plt.subplot(1, 2, 1)
ax.plot(temperature, months)
ax = plt.subplot(1, 2, 2)
ax.plot(temperature, flights_to_hawaii, 'o')
Plotting a graph with axes with the different names inside a subplot:
from matplotlib import pyplot as plt
plt.figure(figsize=(10,5))
ax1 = plt.subplot(1, 2, 1)
ax1.plot(temperature, months)
ax2 = plt.subplot(1, 2, 2)
ax2.plot(temperature, flights_to_hawaii, 'o')
Plotting a graph without axes inside a subplot:
from matplotlib import pyplot as plt
plt.figure(figsize=(10,5))
plt.subplot(1, 2, 1)
plt.plot(temperature, months)
plt.subplot(1, 2, 2)
plt.plot(temperature, flights_to_hawaii, 'o')
This is actually a great question and the first comment points to a good answer.
The summary is this:
They are both the same for simple plots where there is one line and one axis.
The difference is best highlighted here with this piece of code where we can see the usage of two separate axis (with different colours and scales). ax1 and ax2 will be different.
import numpy as np
from matplotlib import pyplot as plt
# generate some data
time = np.arange(0., 10., 0.2)
velocity = np.zeros_like(time, dtype=float)
distance = np.zeros_like(time, dtype=float)
g = 9.8 # m/s^2
velocity = g * time
distance = 0.5 * g * np.power(time, 2)
# create a plot with TWO acis
fig, ax1 = plt.subplots()
ax1.set_ylabel("distance (m)", color="blue")
ax1.set_xlabel("time")
ax1.plot(time, distance, "blue")
ax1.set_yticks(np.linspace(*ax1.get_ybound(), 10))
ax1.tick_params(axis="y", labelcolor="blue")
ax1.xaxis.grid()
ax1.yaxis.grid()
ax2 = ax1.twinx() # create another y-axis sharing a common x-axis
ax2.set_ylabel("velocity (m/s)", color="green")
ax2.set_xlabel("time")
ax2.tick_params(axis="y", labelcolor="green")
ax2.plot(time, velocity, "green")
ax2.set_yticks(np.linspace(*ax2.get_ybound(), 10))
fig.set_size_inches(7,5)
fig.set_dpi(100)
fig.legend(["Distance", "Velocity"])
plt.show()
Which gives this:
Here we have controlled the two separate axis: ax1 and ax2 and plotted on the same chart.

How to rotate axis label and hide some of them?

I was trying to plot a time series and its differentiation.
However, I have two problems with the x axis label:
it's not rotating;
there is too many months and too little space in the canvas.
How can I rotate all labels and hide a few dates?
I can't show the data because of confidentiality. But it's basically a (numeric) column with the series and the (date) index.
This is what I've done so far:
import numpy as np, pandas as pd
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
import matplotlib.pyplot as plt
plt.rcParams.update({'figure.figsize':(9,7), 'figure.dpi':120})
# Original Series
fig, axes = plt.subplots(3, 2, sharex=True);
axes[0, 0].plot(df.teste);
axes[0, 0].set_title('Original Series');
axes[0,0].set_xticklabels(df.index,rotation=90)
plot_acf(df.teste, ax=axes[0, 1]);
# 1st Differencing
axes[1, 0].plot(df.teste.diff());
axes[1, 0].set_title('1st Order Differencing');
plot_acf(df.teste.diff().dropna(), ax=axes[1, 1]);
# 2nd Differencing
axes[2, 0].plot(df.teste.diff().diff());
axes[2, 0].set_title('2nd Order Differencing');
axes[2,0].set_xticklabels(df.index,rotation=90)
plot_acf(df.teste.diff().diff().dropna(), ax=axes[2, 1]);
This is the output:
Check this code:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 1000)
y = np.sin(x)
fig, ax = plt.subplots(1, 2, figsize = (8, 4))
ax[0].plot(x, y, 'r-', lw = 2)
ax[0].set_xticks(np.arange(0, 10, 0.25))
ax[1].plot(x, y, 'r-', lw = 2)
ax[1].set_xticks(np.arange(0, 10, 1))
locs, labels = plt.xticks()
plt.setp(labels, rotation = 90)
plt.show()
which gives me this plot as an example:
As you can see, both graph have the same options, but in the second one (on the right side) I set:
ax[1].set_xticks(np.arange(0, 10, 1))
to space the xticks in order to remove some of them, and
locs, labels = plt.xticks()
plt.setp(labels, rotation = 90)
to rotate their orientations.

How to add a subplot to each rectangle in a Tree Map?

I created this tree map using Matplotlib and Squarify:
Now I would like to add a line plot on each rectangle in the tree map. Is that possible?
Squarify's plot is a convenience function to directly plot a treemap given values and labels. But, this process can also be executed step-by-step. One of the steps is to calculate the positions of the rectangles, for which we suppose a figure which has coordinates from 0,0 to 1,1 from lower left to upper right.
With these rectangles we can manually position axes to draw on. It is unclear whether ticks are needed. If needed, they can be placed inside. Or the axes could be moved completely to the center of each subplot. Or only have ticks without labels.
Here is some demonstrating code:
import numpy as np
import matplotlib.pyplot as plt
import squarify
values = [500, 433, 331, 254, 119]
values.sort(reverse=True) # values must be sorted descending (and positive)
# the sum of the values must equal the total area to be laid out; i.e., sum(values) == width * height
values = squarify.normalize_sizes(values, 1, 1)
rects = squarify.squarify(values, 0, 0, 1, 1)
fig = plt.figure(figsize=(7, 5))
axes = [fig.add_axes([rect['x'], rect['y'], rect['dx'], rect['dy'], ]) for rect in rects]
for ax, color in zip(axes, plt.cm.Pastel1.colors):
x = np.linspace(0, 10, 100)
y = np.random.normal(0.01, 0.1, 100).cumsum()
ax.plot(x, y)
ax.tick_params(axis="y", direction="in", pad=-15)
ax.tick_params(axis="x", direction="in", pad=-15)
plt.setp(ax.get_yticklabels(), ha="left")
ax.set_facecolor(color)
plt.show()
Here is another example resembling the image in the question, with a main plot and a colorbar. The default mplcursors gets confused with all these axes, but annotations while hovering can also be added manually.
import numpy as np
import matplotlib.pyplot as plt
import squarify
values = [4000, 1500, 1500, 1200, 1000, 500]
fig, mainax = plt.subplots(figsize=(6, 4))
mainax.set_xlim(0, 1000)
mainax.set_ylim(0, 1000)
mainax.grid(False)
cmap = plt.cm.get_cmap('Greens')
norm = plt.Normalize(vmin=0, vmax=max(values))
plt.colorbar(plt.cm.ScalarMappable(cmap=cmap, norm=norm))
pos = mainax.get_position()
values.sort(reverse=True)
normalized_values = squarify.normalize_sizes(values, pos.width, pos.height)
rects = squarify.squarify(normalized_values, pos.x0, pos.y0, pos.width, pos.height)
axes = [fig.add_axes([rect['x'], rect['y'], rect['dx'], rect['dy'], ]) for rect in rects]
for ax, val in zip(axes, values):
x = np.linspace(0, 10, 100)
y = np.random.normal(0.01, 0.1, 100).cumsum()
ax.plot(x, y)
ax.set_xticks([])
ax.set_yticks([])
ax.set_facecolor(cmap(norm(val)))
mainax.set_facecolor('none') # prevent that the mainax blocks the other axes
mainax.set_zorder(20) # high z-order because the annotations are drawn using this ax
labels = ['a', 'b', 'c', 'd', 'e', 'f']
sum_val = sum(values)
annotations = [mainax.annotate(f"'{lbl}': {val}\n{val * 100.0 / sum_val:.1f} %",
xy=(0, 0), xycoords='figure pixels',
xytext=(0, 0), textcoords='offset points',
bbox=dict(boxstyle='round', fc='lemonchiffon'),
ha='center', va='bottom')
for ax, val, lbl in zip(axes, values, labels)]
for annot in annotations:
annot.set_visible(False)
def hover(event):
for ax, annot in zip(axes, annotations):
if ax.bbox.contains(event.x, event.y):
annot.xy = (event.x, event.y)
annot.set_visible(True)
else:
annot.set_visible(False)
fig.canvas.draw_idle()
fig.canvas.mpl_connect("motion_notify_event", hover)
plt.show()
Yes it is possible. You will have to write the code to extract the exact positions where you want to place the new plot in.
You need to set the position of the new figure using f.canvas.manager.window.SetPosition
This answer will greatly help https://stackoverflow.com/a/37999370/4551984

Reduce space inside the boxplot matplotlib

I want to remove the extra space inside the plot's border
plt.boxplot(parkingData_agg['occupancy'], 0, 'rs', 0, 0.75)
plt.tight_layout() # This didn't work. Maybe it's not for the purpose I am thinking it is used for.
plt.yticks([0],['Average Occupancy per slot'])
fig = plt.figure(figsize=(5, 1), dpi=5) #Tried to change the figsize but it didn't work
plt.show()
The desired plot is as shown in the 2nd plot from left in the diagram below
The order of commands in the code is a bit chaotic.
You need to define a figure, before the plotting command (otherwise a second figure is produced).
You also need to call tight_layout after setting the ticklabels, such that the long ticklabel can be accounted for.
To have the tick at position 0 match the position of the boxplot, it would need to be set to that position (pos=[0])
Those changes would lead to the following plot
import matplotlib.pyplot as plt
import numpy as np
data = np.random.rayleigh(scale=7, size=100)
fig = plt.figure(figsize=(5, 2), dpi=100)
plt.boxplot(data, False, sym='rs', vert=False, whis=0.75, positions=[0])
plt.yticks([0],['Average Occupancy per slot'])
plt.tight_layout()
plt.show()
You may then change the widths of the boxplot(s) to match the desired outcome, e.g.
plt.boxplot(..., widths=[0.75])
You may of course put your plot in a subplot, not to have the axes fill the entire space of the figure, e.g.
import matplotlib.pyplot as plt
import numpy as np
data = np.random.rayleigh(scale=7, size=100)
fig = plt.figure(figsize=(5, 3), dpi=100)
ax = plt.subplot(3,1,2)
ax.boxplot(data, False, sym='rs', vert=False, whis=0.75, positions=[0], widths=[0.5])
plt.yticks([0],['Average Occupancy per slot'])
plt.tight_layout()
plt.show()
use subplots_adjust
fig = plt.figure(figsize=(5, 2))
axes = fig.add_subplot(1,1,1)
axes.boxplot(parkingData_agg['occupancy'], 0, 'rs', 0, 0.75)
plt.subplots_adjust(left=0.1, right=0.9, top=0.6, bottom=0.4)
#plt.boxplot(parkingData_agg['occupancy'], 0, 'rs', 0, 0.75)
#plt.tight_layout()
plt.yticks([0],['Average Occupancy per slot'])
plt.show()

Categories

Resources