matplotlib超载绘图功能

时间:2018-03-22 11:54:48

标签: python python-3.x matplotlib

我不知道我究竟想要做什么。我也是Python的新用户。基本上我创建了一个非常简单的类,用于表示形式的测量量

enter image description here

并自动执行错误传播。这是定义:

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

现在我想要的是重载matplotlib绘图函数,以便告诉它应该如何绘制,即,使用magnitude.val值的粗线和围绕它的两条细线,幅度为.err。< / p>

我该怎么做?

2 个答案:

答案 0 :(得分:1)

我不知道在matplotlib中重载绘图函数的方法。但是,您可以创建一个模拟matplotlib绘图函数的新函数。

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

最后,我建议在错误行之间使用填充,而不是粗细和两条细线。

答案 1 :(得分:1)

感谢Yngve Moe的回答,我设法编写了这个函数

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

完全符合我的要求。我在这里举一个例子

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

产生这个情节:

enter image description here