[Matplotlib-users] Writing a custom Scale class

Paul Hobson pmhobson at gmail.com
Thu Mar 21 18:21:12 EDT 2019


When the axes limits go beyond the bounds of your dataframe, how should the
ticks be labeled?


On Thu, Mar 21, 2019 at 2:03 PM Konstantin Miller <
konstantin.miller at gmail.com> wrote:

> Hi,
>
> I have a time series with two values x(t) and y(t), stored in a Pandas
> data frame df with columns DateTime, ValueX, ValueY. I would like to plot
> ValueY vs. ValueX. In addition, I would like to see for each data point on
> the graph the date when it was measured.
>
> My idea was to plot(ValueX, ValueY) and then somehow set the labels to
> DateTime. But not only the visible tick labels. Rather, when I move my
> mouse over the plot, I would like to see  (DateTime, ValueY) for each
> point, rather then (ValueX, ValueY).
>
> Or, another way to see it is, that I would like to plot ValueY vs.
> DateTime, but scale the x-axis as ValueX.
>
> My take was to plt.plot(df.index, df.ValueX), and to write a custom Scale
> module that receives the data frame upon construction and that scales the
> x-axis as ValueX and formats the labels as DateTime.
>
> I managed to have the right scaling of my x-axis but I can't see any ticks
> nor tick labels. And when I hover the mouse over the plot, I see (x=nan,
> y=<correct value>).
>
> I appreciate any help! :) It seems that the documentation for such
> low-level functionality is sometimes a bit scarce :)
>
> Here is a minimum working example:
>
> import matplotlib.scale
> import matplotlib.transforms
> import matplotlib.pyplot as plt
> from matplotlib.ticker import AutoLocator, FixedLocator, FuncFormatter,
> MaxNLocator, ScalarFormatter
> import numpy as np
> from numpy import ma
> import pandas as pd
>
> class Scaler(matplotlib.scale.ScaleBase):
>
>     name = 'scaler'
>
>     def __init__(self, axis, df, **kwargs):
>         matplotlib.scale.ScaleBase.__init__(self)
>         self.df = df
>
>     def get_transform(self):
>         return self.Transform(self.df)
>
>     def limit_range_for_scale(self, vmin, vmax, minpos):
>         min_ = max(vmin, self.df.index.min())
>         max_ = min(vmax, self.df.index.max())
>         return min_, max_
>
>     def set_default_locators_and_formatters(self, axis):
>         axis.set_major_locator(AutoLocator())
>         axis.set_major_formatter(ScalarFormatter())
>
>     class Transform(matplotlib.transforms.Transform):
>         input_dims = 1
>         output_dims = 1
>         is_separable = True
>         has_inverse = True
>
>         def __init__(self, df):
>             matplotlib.transforms.Transform.__init__(self)
>             self.df = df
>
>         def transform_non_affine(self, x):
>             if x.ndim > 1:
>                 assert x.ndim == 2 and x.shape[1] == 1
>             y = ma.masked_array(np.zeros_like(x), mask=[False for _ in x])
>             for i in range(x.shape[0]):
>                 if x.ndim == 1:
>                     if (int(x[i]) != x[i]) or (x[i] not in df.index):
>                         y.mask[i] = True
>                     else:
>                         y[i] = self.df.at[int(x[i]), 'x']
>                 else:
>                     if (int(x[i, 0]) != x[i, 0]) or (x[i, 0] not in
> df.index):
>                         y.mask[i] = True
>                     else:
>                         y[i, 0] = self.df.at[int(x[i, 0]), 'x']
>             return y
>
>         def inverted(self):
>             return Scaler.InvertedTransform(self.df)
>
>     class InvertedTransform(matplotlib.transforms.Transform):
>         input_dims = 1
>         output_dims = 1
>         is_separable = True
>         has_inverse = True
>
>         def __init__(self, df):
>             matplotlib.transforms.Transform.__init__(self)
>             self.df = df
>
>         def transform_non_affine(self, x):
>             if x.ndim > 1:
>                 assert x.ndim == 2 and x.shape[1] == 1
>             y = ma.masked_array(np.zeros_like(x), mask=[False for _ in x])
>             for i in range(x.shape[0]):
>                 if x.ndim == 1:
>                     if x[i] not in df['x']:
>                         y.mask[i] = True
>                     else:
>                         y[i] = self.df.loc[self.df['x'] == x[i],
> :].index[0]
>                 else:
>                     if x[i, 0] not in df['x']:
>                         y.mask[i] = True
>                     else:
>                         y[i, 0] = self.df.loc[self.df['x'] == x[i, 0],
> :].index[0]
>             return y
>
>         def inverted(self):
>             return Scaler.Transform(self.df)
>
> matplotlib.scale.register_scale(Scaler)
>
> df = pd.DataFrame(index=range(1, 11), data={'x': [1, 1.5, 3, 3.5, 5, 5.5,
> 7, 7.5, 9, 9.5], 'y': range(1, 11)})
> fig = plt.figure()
> ax = fig.add_subplot(1, 1, 1)
> ax.plot(df.index, df['y'])
> ax.set_xlim([df.index[0], df.index[-1]])
> ax.set_xscale('scaler', df=df)
>
> Cheers
> Konstantin
>
> --
> To send me an encrypted email, download my public key from pgp.mit.edu
> _______________________________________________
> Matplotlib-users mailing list
> Matplotlib-users at python.org
> https://mail.python.org/mailman/listinfo/matplotlib-users
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/matplotlib-users/attachments/20190321/7f6ed6ee/attachment.html>


More information about the Matplotlib-users mailing list