可组合循环

版本:

0.12

日期:

2024 年 1 月 18 日

文档

https://matplotlib.net.cn/cycler

PyPI

https://pypi.python.org/pypi/Cycler

GitHub

https://github.com/matplotlib/cycler

cycler API

cycler()

从单个位置参数、一对位置参数或关键字参数的组合创建一个新的 Cycler 对象。

Cycler(left[, right, op])

可组合循环。

concat(left, right)

连接 Cyclers,就像使用 itertools.chain 链式操作一样。

cycler 的公共 API 包含一个类 Cycler,一个工厂函数 cycler(),以及一个连接函数 concat()。工厂函数提供了一个简单的接口来创建“基本” Cycler 对象,而类则负责组合和迭代逻辑。

Cycler 用法

基本

单个 Cycler 对象可以轻松地循环遍历单个样式。要创建 Cycler,请使用 cycler() 函数将键/样式/关键字参数链接到一系列值。键必须是可散列的(因为它最终将用作 dict 中的键)。

In [1]: from __future__ import print_function

In [2]: from cycler import cycler

In [3]: color_cycle = cycler(color=['r', 'g', 'b'])

In [4]: color_cycle
Out[4]: cycler('color', ['r', 'g', 'b'])

Cycler 知道它的长度和键

In [5]: len(color_cycle)
Out[5]: 3

In [6]: color_cycle.keys
Out[6]: {'color'}

遍历此对象将产生一系列以标签为键的 dict 对象

In [7]: for v in color_cycle:
   ...:     print(v)
   ...: 
{'color': 'r'}
{'color': 'g'}
{'color': 'b'}

Cycler 对象可以作为参数传递给 cycler(),它将返回一个新的 Cycler,它具有新的标签,但值相同。

In [8]: cycler(ec=color_cycle)
Out[8]: cycler('ec', ['r', 'g', 'b'])

遍历 Cycler 会产生有限的条目列表,要获得无限循环,请调用 Cycler 对象(类似于生成器)

In [9]: cc = color_cycle()

In [10]: for j, c in zip(range(5),  cc):
   ....:     print(j, c)
   ....: 
0 {'color': 'r'}
1 {'color': 'g'}
2 {'color': 'b'}
3 {'color': 'r'}
4 {'color': 'g'}

组合

单个 Cycler 可以很容易地用单个 for 循环替换。 Cycler 对象的强大之处在于它们可以组合起来轻松创建复杂的多个键循环。

加法

具有不同键的等长Cycler可以相加,得到两个循环的“内积”。

In [11]: lw_cycle = cycler(lw=range(1, 4))

In [12]: wc = lw_cycle + color_cycle

结果具有相同的长度,并且其键是两个输入Cycler的并集。

In [13]: len(wc)
Out[13]: 3

In [14]: wc.keys
Out[14]: {'color', 'lw'}

遍历结果相当于对两个输入循环进行zip操作。

In [15]: for s in wc:
   ....:     print(s)
   ....: 
{'lw': 1, 'color': 'r'}
{'lw': 2, 'color': 'g'}
{'lw': 3, 'color': 'b'}

与算术运算一样,加法满足交换律。

In [16]: lw_c = lw_cycle + color_cycle

In [17]: c_lw = color_cycle + lw_cycle

In [18]: for j, (a, b) in enumerate(zip(lw_c, c_lw)):
   ....:    print('({j}) A: {A!r} B: {B!r}'.format(j=j, A=a, B=b))
   ....: 
(0) A: {'lw': 1, 'color': 'r'} B: {'color': 'r', 'lw': 1}
(1) A: {'lw': 2, 'color': 'g'} B: {'color': 'g', 'lw': 2}
(2) A: {'lw': 3, 'color': 'b'} B: {'color': 'b', 'lw': 3}

为了方便起见,cycler()函数可以包含多个键值对,并会自动将它们组合成单个Cycler,通过加法实现。

In [19]: wc = cycler(c=['r', 'g', 'b'], lw=range(3))

In [20]: for s in wc:
   ....:     print(s)
   ....: 
{'c': 'r', 'lw': 0}
{'c': 'g', 'lw': 1}
{'c': 'b', 'lw': 2}

乘法

任何一对Cycler都可以相乘。

In [21]: m_cycle = cycler(marker=['s', 'o'])

In [22]: m_c = m_cycle * color_cycle

这将得到两个循环的“外积”(与itertools.product()相同)。

In [23]: len(m_c)
Out[23]: 6

In [24]: m_c.keys
Out[24]: {'color', 'marker'}

In [25]: for s in m_c:
   ....:     print(s)
   ....: 
{'marker': 's', 'color': 'r'}
{'marker': 's', 'color': 'g'}
{'marker': 's', 'color': 'b'}
{'marker': 'o', 'color': 'r'}
{'marker': 'o', 'color': 'g'}
{'marker': 'o', 'color': 'b'}

注意,与加法不同,乘法不满足交换律(类似于矩阵)。

In [26]: c_m = color_cycle * m_cycle

In [27]: for j, (a, b) in enumerate(zip(c_m, m_c)):
   ....:    print('({j}) A: {A!r} B: {B!r}'.format(j=j, A=a, B=b))
   ....: 
(0) A: {'color': 'r', 'marker': 's'} B: {'marker': 's', 'color': 'r'}
(1) A: {'color': 'r', 'marker': 'o'} B: {'marker': 's', 'color': 'g'}
(2) A: {'color': 'g', 'marker': 's'} B: {'marker': 's', 'color': 'b'}
(3) A: {'color': 'g', 'marker': 'o'} B: {'marker': 'o', 'color': 'r'}
(4) A: {'color': 'b', 'marker': 's'} B: {'marker': 'o', 'color': 'g'}
(5) A: {'color': 'b', 'marker': 'o'} B: {'marker': 'o', 'color': 'b'}

整数乘法

Cycler也可以乘以整数值,以增加长度。

In [28]: color_cycle * 2
Out[28]: cycler('color', ['r', 'g', 'b', 'r', 'g', 'b'])

In [29]: 2 * color_cycle
Out[29]: cycler('color', ['r', 'g', 'b', 'r', 'g', 'b'])

连接

Cycler对象可以通过Cycler.concat()方法进行连接。

In [30]: color_cycle.concat(color_cycle)
Out[30]: cycler('color', ['r', 'g', 'b', 'r', 'g', 'b'])

或者使用顶层concat()函数。

In [31]: from cycler import concat

In [32]: concat(color_cycle, color_cycle)
Out[32]: cycler('color', ['r', 'g', 'b', 'r', 'g', 'b'])

切片

可以使用 slice 对象对循环进行切片

In [33]: color_cycle[::-1]
Out[33]: cycler('color', ['b', 'g', 'r'])

In [34]: color_cycle[:2]
Out[34]: cycler('color', ['r', 'g'])

In [35]: color_cycle[1:]
Out[35]: cycler('color', ['g', 'b'])

以返回循环的子集作为新的 Cycler

检查 Cycler

要检查转置的 Cycler 的值,请使用 Cycler.by_key 方法

In [36]: c_m.by_key()
Out[36]: 
{'marker': ['s', 'o', 's', 'o', 's', 'o'],
 'color': ['r', 'r', 'g', 'g', 'b', 'b']}

这个 dict 可以被修改,并用于创建一个新的 Cycler,其中包含更新后的值

In [37]: bk = c_m.by_key()

In [38]: bk['color'] = ['green'] * len(c_m)

In [39]: cycler(**bk)
Out[39]: (cycler('marker', ['s', 'o', 's', 'o', 's', 'o']) + cycler('color', ['green', 'green', 'green', 'green', 'green', 'green']))

示例

我们可以使用 Cycler 实例来循环一个或多个 kwargplot

from cycler import cycler
from itertools import cycle

fig, (ax1, ax2) = plt.subplots(1, 2, tight_layout=True,
                               figsize=(8, 4))
x = np.arange(10)

color_cycle = cycler(c=['r', 'g', 'b'])

for i, sty in enumerate(color_cycle):
   ax1.plot(x, x*(i+1), **sty)


for i, sty in zip(range(1, 5), cycle(color_cycle)):
   ax2.plot(x, x*i, **sty)

(Source code, png, hires.png, pdf)

_images/index-1.png
from cycler import cycler
from itertools import cycle

fig, (ax1, ax2) = plt.subplots(1, 2, tight_layout=True,
                               figsize=(8, 4))
x = np.arange(10)

color_cycle = cycler(c=['r', 'g', 'b'])
ls_cycle = cycler('ls', ['-', '--'])
lw_cycle = cycler('lw', range(1, 4))

sty_cycle = ls_cycle * (color_cycle + lw_cycle)

for i, sty in enumerate(sty_cycle):
   ax1.plot(x, x*(i+1), **sty)

sty_cycle = (color_cycle + lw_cycle) * ls_cycle

for i, sty in enumerate(sty_cycle):
   ax2.plot(x, x*(i+1), **sty)

(Source code, png, hires.png, pdf)

_images/index-2.png

持久循环

将给定标签与样式通过字典查找关联起来,并动态生成该映射可能很有用。这可以使用 defaultdict 很容易实现。

In [40]: from cycler import cycler as cy

In [41]: from collections import defaultdict

In [42]: cyl = cy('c', 'rgb') + cy('lw', range(1, 4))

要获得有限的样式集

In [43]: finite_cy_iter = iter(cyl)

In [44]: dd_finite = defaultdict(lambda : next(finite_cy_iter))

或重复

In [45]: loop_cy_iter = cyl()

In [46]: dd_loop = defaultdict(lambda : next(loop_cy_iter))

这在绘制具有分类和标签的复杂数据时可能很有帮助

finite_cy_iter = iter(cyl)
styles = defaultdict(lambda : next(finite_cy_iter))
for group, label, data in DataSet:
    ax.plot(data, label=label, **styles[group])

这将导致每个具有相同 groupdata 使用相同的样式绘制。

异常

如果添加了长度不等的 Cycler,则会引发 ValueError

In [47]: cycler(c=['r', 'g', 'b']) + cycler(ls=['-', '--'])
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[47], line 1
----> 1 cycler(c=['r', 'g', 'b']) + cycler(ls=['-', '--'])

File ~/code/cycler/venv/lib64/python3.12/site-packages/cycler/__init__.py:283, in Cycler.__add__(self, other)
    275 """
    276 Pair-wise combine two equal length cyclers (zip).
    277 
   (...)
    280 other : Cycler
    281 """
    282 if len(self) != len(other):
--> 283     raise ValueError(
    284         f"Can only add equal length cycles, not {len(self)} and {len(other)}"
    285     )
    286 return Cycler(
    287     cast(Cycler[Union[K, L], Union[V, U]], self),
    288     cast(Cycler[Union[K, L], Union[V, U]], other),
    289     zip
    290 )

ValueError: Can only add equal length cycles, not 3 and 2

或者如果组合了具有重叠键的两个循环

In [48]: color_cycle = cycler(c=['r', 'g', 'b'])

In [49]: color_cycle + color_cycle
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[49], line 1
----> 1 color_cycle + color_cycle

File ~/code/cycler/venv/lib64/python3.12/site-packages/cycler/__init__.py:286, in Cycler.__add__(self, other)
    282 if len(self) != len(other):
    283     raise ValueError(
    284         f"Can only add equal length cycles, not {len(self)} and {len(other)}"
    285     )
--> 286 return Cycler(
    287     cast(Cycler[Union[K, L], Union[V, U]], self),
    288     cast(Cycler[Union[K, L], Union[V, U]], other),
    289     zip
    290 )

File ~/code/cycler/venv/lib64/python3.12/site-packages/cycler/__init__.py:179, in Cycler.__init__(self, left, right, op)
    176 else:
    177     self._right = None
--> 179 self._keys: set[K] = _process_keys(self._left, self._right)
    180 self._op: Any = op

File ~/code/cycler/venv/lib64/python3.12/site-packages/cycler/__init__.py:84, in _process_keys(left, right)
     82 r_key: set[K] = set(r_peek.keys())
     83 if l_key & r_key:
---> 84     raise ValueError("Can not compose overlapping cycles")
     85 return l_key | r_key

ValueError: Can not compose overlapping cycles

动机

在绘制多条线时,通常希望能够循环遍历一个或多个艺术家样式。对于简单的案例,这可以在没有太多麻烦的情况下完成

fig, ax = plt.subplots(tight_layout=True)
x = np.linspace(0, 2*np.pi, 1024)

for i, (lw, c) in enumerate(zip(range(4), ['r', 'g', 'b', 'k'])):
   ax.plot(x, np.sin(x - i * np.pi / 4),
           label=r'$\phi = {{{0}}} \pi / 4$'.format(i),
           lw=lw + 1,
           c=c)

ax.set_xlim([0, 2*np.pi])
ax.set_title(r'$y=\sin(\theta + \phi)$')
ax.set_ylabel(r'[arb]')
ax.set_xlabel(r'$\theta$ [rad]')

ax.legend(loc=0)

(Source code, png, hires.png, pdf)

_images/index-3.png

但是,如果你想做更复杂的事情

fig, ax = plt.subplots(tight_layout=True)
x = np.linspace(0, 2*np.pi, 1024)

for i, (lw, c) in enumerate(zip(range(4), ['r', 'g', 'b', 'k'])):
   if i % 2:
       ls = '-'
   else:
       ls = '--'
   ax.plot(x, np.sin(x - i * np.pi / 4),
           label=r'$\phi = {{{0}}} \pi / 4$'.format(i),
           lw=lw + 1,
           c=c,
           ls=ls)

ax.set_xlim([0, 2*np.pi])
ax.set_title(r'$y=\sin(\theta + \phi)$')
ax.set_ylabel(r'[arb]')
ax.set_xlabel(r'$\theta$ [rad]')

ax.legend(loc=0)

(源代码, png, hires.png, pdf)

_images/index-4.png

绘图逻辑很快就会变得非常复杂。为了解决这个问题并允许轻松地循环遍历任意 kwargs,开发了 Cycler 类,这是一个可组合的关键字参数迭代器。