0

I don't know how exactly is called what I want to do. I'm also a new user of Python. Basically I have created a very simple class that is intended to represent measured quantities of the form

enter image description here

and automatically do the error propagation. Here's the definition:

from math import sqrt
from math import log10
from math import fabs
from math import floor
from math import ceil
from math import pi

class magnitude:
    def __init__(self, val, err):
        self.val = val
        self.err = err

    def __str__(self):
        "Prints the magnitude in the format v.vvvv(v+-e) being 'v' the relevant digits of the magnitude value and '+-e' the most significant digit of the error."
        if self.err == 0 or self.val == 0:
            return str(self.val)
        if self.err >= fabs(self.val):
            return '{:.0e} +- {:.0e}'.format(self.val, self.err) + ' (Infinite uncertainty!)'
        else:
            temp = '{:.' + str(ceil(log10(fabs(self.val*(1+pi*1e-3))) - ceil(log10(self.err*(1+pi*1e-3))) )) + 'e}' # Calculates number of digits for the representative part. The adition of 'pi*1e-3' is to add some noise and avoid error values of '1', '10', '100', ..., because they are not well printed.
            temp = temp.format(self.val)
            temp = temp[:-4] # Remove last 4 chars.
            return temp + '(+-' + str(int(self.err*10**(ceil(-log10(self.err))))) + ')' + '{:.0e}'.format(fabs(self.val))[1:] + ' ({:d} ppm)'.format(int(self.err/fabs(self.val)*1e6))
    # --------------------
    def __add__(self, other):
        if type(other) == int or type(other) == float:
            other = magnitude(other, 0)
        return magnitude(self.val + other.val, sqrt(self.err**2 + other.err**2))
    def __radd__(self, other):
        if type(other) == int or type(other) == float:
            other = magnitude(other, 0)
        return magnitude(self.val + other.val, sqrt(self.err**2 + other.err**2))
    # --------------------
    def __sub__(self, other):
        if type(other) == int or type(other) == float:
            other = magnitude(other, 0)
        return magnitude(self.val - other.val, sqrt(self.err**2 + other.err**2))
    def __rsub__(self, other):
        if type(other) == int or type(other) == float:
            other = magnitude(other, 0)
        return magnitude(-self.val + other.val, sqrt(self.err**2 + other.err**2))
    # --------------------
    def __mul__(self, other):
        if type(other) == int or type(other) == float:
            other = magnitude(other, 0)
        return magnitude(self.val*other.val, sqrt(self.val**2*other.err**2 + self   .err**2*other.val**2))
    def __rmul__(self, other):
        if type(other) == int or type(other) == float:
            other = magnitude(other, 0)
        return magnitude(self.val*other.val, sqrt(self.val**2*other.err**2 + self   .err**2*other.val**2))
    # --------------------
    def __truediv__(self, other):
        if type(other) == int or type(other) == float:
            other = magnitude(other, 0)
        return magnitude(self.val/other.val, sqrt(self.err**2/other.val**2 + self.val**2/other.val**2*other.err**2))
    def __rtruediv__(self, other):
        if type(other) == int or type(other) == float:
            other = magnitude(other, 0)
        return magnitude(other.val/self.val, sqrt(other.err**2/self.val**2 + other.val**2/self.val**2*self.err**2))

Now what I want is to overload the matplotlib plot function in order to tell it how this should be plotted, i.e. a bold line for the magnitude.val value and two thin lines surrounding it with a distance of magnitude.err.

How should I do this?

4
  • I don't think you should overload anything. Rather you should define a function that call matplotlib.pyplot.plot Commented Mar 22, 2018 at 12:37
  • I agree to @TomdeGeus. In Python you can only overload operators not functions. Just call plotting mehods. Commented Mar 22, 2018 at 12:48
  • @SvenKrüger No longer true: PEP 3124 Commented Mar 22, 2018 at 17:39
  • @Paul PEP 3124 is "deferred"... Look at the answers to this question. They show the concept of overriding instead of overloading. Commented Mar 23, 2018 at 7:57

2 Answers 2

1

I am not aware of a way to overload the plot function in matplotlib. However, you can create a new function which emulates the matplotlib plot function.

def plot(x=None, y=None, *args, axis=None, **kwargs):
    """Creates a plot from iterable, works with magnitude instances.

    All keyword arguments in matplotlib functions are accepted,
    but `linewidth` should not be used if y contains magnitude instances.

    Parameters
    ----------
    x : Array like
        The x positions of the plot [pomts. If y is not given, y is set to x and
        x is set to range(len(y)).
    y : Array like
        The y positions of the plot points.
    axis : matplotlib.Axes (optional)
        Axes to plot the values in.
    """
    if axis is None:
        axis = plt
    if y is None:
        y = x
        x = range(len(y))

    if isinstance(y[0], magnitude):
        for yi in y:
            if not isinstance(y[i]), magnitude):
                raise ValueError(
                    'Either all or none of the plot positions should '
                    'be magnitude instances'
                )

        y_val = [yi.val for yi in y]
        y_err = [yi.err for yi in y]
        lines = axis.plot(x, y_val, *args, linewidth=2.0, **kwargs)
        lines += axis.plot(x, y_val + y_err, *args, **kwargs)
        lines += axis.plot(x, y_val - y_err, *args, **kwargs)
    else:
        lines = axis.plot(x, y, *args, **kwargs)

    return lines

Lastly, I would recommend using fill between for the error lines instead of a thick and two thin lines.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for your answer!. I don't understand what means x = range(self.val), who is "self" in this case? Could you post an usage example? I am trying to use this function but getting an error exactly in that line.
That was a typo, I mistakenly started on it as a class function and changed my mind, I should have corrected it now
1

Thanks to the answer of Yngve Moe I have managed to write this function

def plot_yerr_filled(x=None, y=None, axis=plt, color=(0,0,0), alpha=0.25, *args, **kwargs):
    """Creates a 'filled error bar' plot. All keyword arguments in matplotlib functions are accepted.

    Parameters
    ----------
    x : Array like (list of float, int, etc. or numpy.array or list of magnitude) of exact 'x' values (i.e. with no error, if 'x' is a list of 'magnitude' type objects, errors are discarded).
    y : Array like (list) of 'magnitude' type elements containing the 'y' values and the 'yerr' associated error.
    axis: matplotlib.axes (optional) in which to plot the data.
    color: tuple (r,g,b) defining the color.
    alpha: float between 0 and 1 indicating the transparency of the filled error bar.
    """

    for i in range(0, len(x)): # Validation of x
        if isinstance(x[i], magnitude):
            x[i] = x[i].val
    for i in range(len(y)): # Validation of y
        if not isinstance(y[i], magnitude):
            raise ValueError('y[' + str(i) + '] is not of type "magnitude"')

    y_val = [y[i].val for i in range(len(y))]
    y_err = [y[i].err for i in range(len(y))]
    axis.fill_between(np.array(x), np.array(y_val)-np.array(y_err), np.array(y_val) + np.array(y_err), alpha=alpha, edgecolor=color, facecolor=color )
    lines = axis.plot(np.array(x), np.array(y_val), color=color, *args, **kwargs)
    return lines

which does exactly what I wanted. Here I let an example

import pymagpy as mg
import matplotlib.pyplot as plt
plt.rc('axes', axisbelow=True) # This is for plotting data in front of the grid.
plt.rcParams.update({'font.size': 8}) # Changes the default font size for the plots.
import numpy as np

x = mg.magnitude(1, 0.1)
y = mg.magnitude(-6798, 6)
z = mg.magnitude(1e-30, .00002872982778297289e-30)

print(x)
print(y)
print(z)
print(x+y/z*2-z/x*y) # Automatically calculates the error propagation.

x_values = []
y_values = []
z_values = []
for k in range(10):
    x_values.append(mg.magnitude(k, k**2))
    y_values.append(mg.magnitude(k**2, k))
    z_values.append(mg.magnitude(np.sqrt(300*k)+1, k/(k-2.2)+5))

FIG_WIDTH = 130e-3 # Figure width in meters (size of the chart+axis_ticks, not counting legend, title, etc.).
FIG_RATIO = [1, 0.8] # XY ratio aspect of figures.
f, (ax1, ax2) = plt.subplots(2, sharex=True, figsize=(FIG_WIDTH*FIG_RATIO[0]/25.4e-3, FIG_WIDTH*FIG_RATIO[1]/25.4e-3)) # Create the figure for plotting.
f.subplots_adjust(hspace=0.1) # Fine-tune figure; make subplots close to each other and hide x ticks for all but bottom plot.
mg.plot_yerr_filled(x_values, y_values, color=(1,0,0), alpha=.1, axis=ax1, label='Y', marker='.')
mg.plot_yerr_filled(x_values, z_values, color=(0,0,1), axis=ax1, label='Z', marker='o', linestyle='')
ax1.set_ylabel('y axis')
ax1.grid(b=True, which='major', color='#aaaaaa', linestyle='-', linewidth=0.5) # Configure the grid in a more fachera way.
ax1.grid(b=True, which='minor', color='#dddddd', linestyle='-', linewidth=0.25) # Configure the grid in a more fachera way.
ax1.minorticks_on() # Enables minor ticks without text, only the ticks.
ax1.legend(loc='best', fancybox=True, framealpha=0.7, ncol=1, fontsize=8)
mg.plot_yerr_filled(x_values, [(a*b)/(a+b) for a,b in zip(y_values, z_values)], axis=ax2, label='(Y*Z)/(Y+Z)', marker='.', linestyle='--')
ax2.set_xlabel('x axis')
ax2.set_ylabel('y axis')
ax2.grid(b=True, which='major', color='#aaaaaa', linestyle='-', linewidth=0.5) # Configure the grid in a more fachera way.
ax2.grid(b=True, which='minor', color='#dddddd', linestyle='-', linewidth=0.25) # Configure the grid in a more fachera way.
ax2.minorticks_on() # Enables minor ticks without text, only the ticks.
ax2.legend(loc='best', fancybox=True, framealpha=0.7, ncol=1, fontsize=8)
plt.savefig('example.pdf', bbox_inches='tight')
plt.show()

which produces this plot:

enter image description here

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.