Honza
Honza

Reputation: 1008

Pyplot - show x-axis labels according to y-axis value

I have 1min 20s long video record of 23.813 FPS. More precisely, I have 1923 frames in which I've been scanning desired features. I've detected some specific behavior via neural network and using chosen metric I calculated a value for each frame.

So, now, I have X-Y values to plot a graph:

X: time (each step of size 0,041993869s)
Y: a value measured by neural network

In the default state, the plot looks like this:

default plot rendering

So, I've tried to limit the number of bins in the faith that the bins will be spread over all my values. But they are not. As you can see, only first fifteen x-values are rendered:

pyplot.locator_params(axis='x', nbins=15)

But neither one is desired state. The desired state should render the labels of such x-bins with y-value higher than e.g. 1.2. So, it should look like this:

desired state

Is possible to achieve such result?

Code:

# draw plot
from pandas import read_csv
from matplotlib import pyplot

test_video_fps = 23.813

df = read_csv('/path/to/csv/file/file.csv', header=None)
df.columns = ['anomaly']

df['time'] = [round((i + 1) / test_video_fps, 2) for i in range(df.shape[0])]

axes = df.plot.bar(x='time', y='anomaly', rot='0')
# pyplot.locator_params(axis='x', nbins=15)
# axes.get_xaxis().set_visible(False)

fig = pyplot.gcf()
fig.set_size_inches(16, 10)
fig.savefig('/path/to/output/plot.png', dpi=100)

# pyplot.show()

Example:

Simple example with a subset of original data.

0.379799
0.383786
0.345488
0.433286
0.469474
0.431993
0.474253
0.418843
0.491070
0.447778
0.384890
0.410994
0.898229
1.872756
2.907009
3.691382
4.685749
4.599612
3.738768
8.043357
7.660785
2.311198
1.956096
2.877326
3.467511
3.896339
4.250552
6.485533
7.452986
7.103761
2.684189
2.516134
1.512196
1.435303
0.852047
0.842551
0.957888
0.983085
0.990608
1.046679
1.082040
1.119655
0.962391
1.263255
1.371034
1.652812
2.160451
2.646674
1.460051
1.163745
0.938030
0.862976
0.734119
0.567076
0.417270

Desired plot:

desired plot of the example

Upvotes: 1

Views: 110

Answers (1)

Mad Physicist
Mad Physicist

Reputation: 114578

Your question has become a two-part problem, but it is interesting enough that I will answer both.

I will answer this in Matplotlib object oriented notation with numpy data rather than pandas. This will make things easier to explain, and can be easily generalized to pandas.

I will assume that you have the following two data arrays:

dt = 0.041993869
x = np.arange(0.0, 15 * dt, dt)
y = np.array([1., 1.1, 1.3, 7.6, 2.4, 0.8, 0.7, 0.8, 1.0, 1.5, 10.0, 4.5, 3.2, 0.9, 0.7])

Part 1: Identifying the locations where you want labels

The data can be masked to get the locations of the peaks:

mask = y > 1.2

Consecutive peaks can be easily eliminated by computing the diff. A diff of a boolean mask will be True at the locations where the mask changes sense. You will then have to take every other element to get the locations where it goes from False to True. The following code will capture all the corner cases where you start with a peak or end in the middle of a peak:

d = np.flatnonzero(np.diff(mask))
if mask[d[0]]:  # First diff is end of peak: True to False
    d = np.concatenate(([0], d[1::2] + 1))
else:
    d = d[::2] + 1

d is now an array indices into x and y that represent the first element of each run of peaks. You can get the last element by swapping the indices [1::2] and [::2] in the if-else statement, and removing the + 1 in both cases.

The locations of the labels are now simply x[d].

Part 2: Locating and formatting the labels

For this part, you will need to access Matplotlib's object oriented API via the Axes object you are plotting on. You already have this in the pandas form, making the transfer easy. Here is a sample in raw Matplotlib:

fig, axes = plt.subplots()
axes.plot(x, y)

Now use the ticker API to easily set the locations and labels. You actually set the locations directly (not with a Locator) since you have a very fixed list of ticks:

axes.set_xticks(x[d])
axes.xaxis.set_major_formatter(ticker.StrMethodFormatter('{x:0.01g}s'))

For the sample data show here, you get

enter image description here

Upvotes: 2

Related Questions