用“值”来绘制Matplotlib颜色线。

5
这个问题的各种版本之前已经被问过,我不确定是否应该在其中一个线程上提出我的问题或者开启一个新线程。以下是我的问题:
我有一个pandas数据帧,在其中有一列(例如:速度)我正在尝试绘制,并且还有另一列(例如:活跃),目前为止是true/false。根据active的值,我想着色线条图。
这个线程似乎是“正确”的解决方案,但是我遇到了一个问题: seaborn or matplotlib line chart, line color depending on variable 我和OP都试图实现同样的事情:

contiguous multi-colored line

这是一个坏掉的图表/复现器:

Values=[3,4,6, 6,5,4, 3,2,3, 4,5,6]
Colors=['red','red', 'red', 'blue','blue','blue', 'red', 'red', 'red', 'blue', 'blue', 'blue']
myf = pd.DataFrame({'speed': Values, 'colors': Colors})

grouped = myf.groupby('colors')
fig, ax = plt.subplots(1)

for key, group in grouped:
   group.plot(ax=ax, y="speed", label=key, color=key)

生成的图表存在两个问题:改变颜色的线条不仅没有“连接”,而且颜色本身也“横跨”端点。

lines are non-contiguous

我希望看到的是从红色到蓝色再回来的变化看起来像一个连续的线条。 第三个变量的颜色线 - Python似乎做了正确的事情,但我没有处理“线性”颜色数据。我基本上是在一列中分配一组线条颜色。我可以轻松地将颜色列的值设置为数字:
Colors=['1','1', '1', '2','2'...]

如果这样做可以更容易地生成所需的绘图。
第一个线程中有一条评论:

当颜色改变时,您可以通过复制点来完成,我已对答案进行了修改

但我基本上是复制并粘贴了答案,所以我不确定那条评论是否完全准确。
2个回答

4

设置

import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

Values=[3,4,6, 6,5,4, 3,2,3, 4,5,6]
Colors=['red','red', 'red', 'blue','blue','blue', 'red', 'red', 'red', 'blue', 'blue', 'blue']
myf = pd.DataFrame({'speed': Values, 'colors': Colors})

解决方案

1. 基于Pandas "diff()" with string,检测颜色变化点并标记连续颜色的子组。

myf['change'] = myf.colors.ne(myf.colors.shift().bfill()).astype(int)
myf['subgroup'] = myf['change'].cumsum()

myf
   colors  speed  change  subgroup
0     red      3       0         0
1     red      4       0         0
2     red      6       0         0
3    blue      6       1         1
4    blue      5       0         1
5    blue      4       0         1
6     red      3       1         2
7     red      2       0         2
8     red      3       0         2
9    blue      4       1         3
10   blue      5       0         3
11   blue      6       0         3

2. 在索引中创建间隙,以便在颜色子组之间容纳重复行

myf.index += myf['subgroup'].values

myf
   colors  speed  change  subgroup
0     red      3       0         0
1     red      4       0         0
2     red      6       0         0
4    blue      6       1         1  # index is now 4; 3 is missing
5    blue      5       0         1
6    blue      4       0         1
8     red      3       1         2  # index is now 8; 7 is missing
9     red      2       0         2
10    red      3       0         2
12   blue      4       1         3  # index is now 12; 11 is missing
13   blue      5       0         3
14   blue      6       0         3

3. 保存每个子组的第一行的索引

first_i_of_each_group = myf[myf['change'] == 1].index

first_i_of_each_group
Int64Index([4, 8, 12], dtype='int64')

4. 将每个组的第一行复制到前一个组的最后一行

for i in first_i_of_each_group:
    # Copy next group's first row to current group's last row
    myf.loc[i-1] = myf.loc[i]
    # But make this new row part of the current group
    myf.loc[i-1, 'subgroup'] = myf.loc[i-2, 'subgroup']
# Don't need the change col anymore
myf.drop('change', axis=1, inplace=True)
myf.sort_index(inplace=True)
# Create duplicate indexes at each subgroup border to ensure the plot is continuous.
myf.index -= myf['subgroup'].values

myf
   colors  speed  subgroup
0     red      3         0
1     red      4         0
2     red      6         0
3    blue      6         0  # this and next row both have index = 3
3    blue      6         1  # subgroup 1 picks up where subgroup 0 left off
4    blue      5         1
5    blue      4         1
6     red      3         1
6     red      3         2
7     red      2         2
8     red      3         2
9    blue      4         2
9    blue      4         3
10   blue      5         3
11   blue      6         3

5. 绘图

fig, ax = plt.subplots()
for k, g in myf.groupby('subgroup'):
    g.plot(ax=ax, y='speed', color=g['colors'].values[0], marker='o')
ax.legend_.remove()

plot output


3
我来试试。根据你提供的其他问题的评论,我找到了这个链接。我必须转到matplotlib才能完成它,无法在pandas本身中完成。一旦我将数据框转换为列表,基本上就是与mpl页面相同的代码。
我创建了与您类似的数据框:
vals=[3,4,6, 6,5,4, 3,2,3, 4,5,6]
colors=['red' if x < 5 else 'blue' for x in vals]
df = pd.DataFrame({'speed': vals, 'danger': colors})

将 vals 和 index 转换为列表

x = df.index.tolist()
y = df['speed'].tolist()
z = np.array(list(y))

将vals和index分解成点,然后用它们创建线段。
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)

根据创建数据框时使用的条件创建颜色映射。在我的情况下,速度小于5为红色,其他为蓝色。
cmap = ListedColormap(['r', 'b'])
norm = BoundaryNorm([0, 4, 10], cmap.N)

创建线段并相应地分配颜色。
lc = LineCollection(segments, cmap=cmap, norm=norm)
lc.set_array(z)

情节!
fig = plt.figure()
plt.gca().add_collection(lc)
plt.xlim(min(x), max(x))
plt.ylim(0, 10)

这是输出结果:

enter image description here

注意:在当前代码中,线段的颜色取决于起点。但希望这能给您一个想法。
我还是新手,如果需要添加/删除一些细节,请让我知道。谢谢!

1
谢谢您的回复!这已经非常接近了。这要求speed的数值对应于颜色。在我的特定情况下,虽然这里没有直接说明,但我是根据数据框中的另一列手动设置颜色,这就是为什么我有明确的颜色分配。这里实际的speed值并不决定颜色。 - Erik Jacobs
我正在尝试做完全相同的事情,即基于另一列设置线条的基础颜色。例如,我有一个第三列,其值为'a'或'b'。我想要根据这一列来设置线段的颜色。你是如何做到的? - J.A.Cado

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接