我可以使用matplotlib.units在图表上显示的单位之间切换吗?

时间:2014-01-21 23:26:05

标签: matplotlib

我正在使用matplotlib在桌面应用程序中渲染图形,我对matplotlib对单位的支持感到有些困惑。我想让用户更改图表的 x 轴上显示的单位:所有计算都将以纳米为单位进行,但图表可以以纳米或千兆赫为单位显示。 (请注意,这两个单位之间存在反向关系,而非线性关系。)

有两种显而易见的方法:“手动”或使用matplotlib。在前一种情况下,matplotlib对单位一无所知;当用户更改单位时,我只需重新计算所有数据点,更改轴的边界和标签,并告诉matplotlib重绘。但我希望matplotlib具有内置功能来做同样的事情。

有一些light documentation about matplotlib.units,但据我所知,这是用于绘制用户指定对象的数组,而不是浮点数组。我的数据实际上是一个浮点数组,由于每个图表有数十万个数据点,我无法将每个值包装在某个自定义模型类中。 (甚至this simple example包装模型类中的每个数据点,整个示例如此通用我无法告诉我如何根据我的需要调整它。)无论如何,原始数据点始终具有相同的单位,因此只需要将值转换为另一个单位。

如何告诉matplotlib我的单位,然后使用axes.set_xunit或其他东西来改变它们?

2 个答案:

答案 0 :(得分:2)

尝试使用函数而不是浮点数进行转换

以下示例可以作为开始。 Gigahertz GHzper nanosecondnm^-1per nano meter,因此我猜这个示例会给nm^-1

我拿了mpl模拟并进行了三次改编:

支持转换的可调用函数

def value( self, unit ):
    if callable(unit):          # To handle a callable function for conversion
        return unit(self._val)
    ...

定义转换功能

one_by_x = lambda x: 1 / x if x != 0 else 0
....

传递

ax.plot( x, y, 'o', xunits=one_by_x )
....
ax.set_title("xunits = 1/x")

您可以使用适当的函数定义一些函数和重绘。 或者 - 对于复杂化 - 甚至添加另一个参数并同时具有:常量缩放因子加函数。

来自http://matplotlib.org/examples/units/evans_test.html的整个改编示例:

"""
A mockup "Foo" units class which supports
conversion and different tick formatting depending on the "unit".
Here the "unit" is just a scalar conversion factor, but this example shows mpl is
entirely agnostic to what kind of units client packages use
"""
from matplotlib.cbook import iterable
import matplotlib.units as units
import matplotlib.ticker as ticker
import matplotlib.pyplot as plt


class Foo:
    def __init__( self, val, unit=1.0 ):
        self.unit = unit
        self._val = val * unit

    def value( self, unit ):
        if callable(unit):
            return unit(self._val)
        if unit is None: unit = self.unit
        return self._val / unit

class FooConverter:

    @staticmethod
    def axisinfo(unit, axis):
        'return the Foo AxisInfo'
        if unit==1.0 or unit==2.0:
            return units.AxisInfo(
                majloc = ticker.IndexLocator( 8, 0 ),
                majfmt = ticker.FormatStrFormatter("VAL: %s"),
                label='foo',
                )

        else:
            return None

    @staticmethod
    def convert(obj, unit, axis):
        """
        convert obj using unit.  If obj is a sequence, return the
        converted sequence
        """
        if units.ConversionInterface.is_numlike(obj):
            return obj

        if iterable(obj):
            return [o.value(unit) for o in obj]
        else:
            return obj.value(unit)

    @staticmethod
    def default_units(x, axis):
        'return the default unit for x or None'
        if iterable(x):
            for thisx in x:
                return thisx.unit
        else:
            return x.unit

units.registry[Foo] = FooConverter()

# create some Foos
x = []
for val in range( 0, 50, 2 ):
    x.append( Foo( val, 1.0 ) )

# and some arbitrary y data
y = [i for i in range( len(x) ) ]

one_by_x = lambda x: 1 / x if x != 0 else 0
# plot specifying units
fig = plt.figure()
fig.suptitle("Custom units")
fig.subplots_adjust(bottom=0.2)
ax = fig.add_subplot(1,2,2)
ax.plot( x, y, 'o', xunits=one_by_x )
for label in ax.get_xticklabels():
    label.set_rotation(30)
    label.set_ha('right')
ax.set_title("xunits = 1/x")


# plot without specifying units; will use the None branch for axisinfo
ax = fig.add_subplot(1,2,1)
ax.plot( x, y ) # uses default units
ax.set_title('default units')
for label in ax.get_xticklabels():
    label.set_rotation(30)
    label.set_ha('right')

plt.show()

答案 1 :(得分:2)

我无法使用matplotlib找到令人满意的方法 - 我不希望在对象中包装每个数据值的开销。在将数据传递给matplotlib之前,我决定自己进行单位转换。