在matplotlib/gnuplot中绘制带标签的区间图。

31

我有一个数据样本,看起来像这样:

a 10:15:22 10:15:30 OK
b 10:15:23 10:15:28 OK
c 10:16:00 10:17:10 FAILED
b 10:16:30 10:16:50 OK

我想要的是按照以下方式绘制上述数据:

captions ^
  |
c |         *------*
b |   *---*    *--*
a | *--*
  |___________________
                     time >

根据数据点的OK/FAILED状态,线条的颜色有所不同。标签(a/b/c/...)可能会重复也可能不会。

我从gnuplotmatplotlib的文档中了解到,后者更容易绘制这种类型的图形,因为它不是标准图形,需要进行一些预处理。

问题是:

  1. 是否有任何工具可以用标准方式绘制这类图形?
  2. 如果没有,该如何绘制这个数据(指向相关工具/文档/函数/示例,这些都能做到类似于这里描述的事情)?
4个回答

28

更新:现在包括处理数据样本并使用mpl日期功能。

import matplotlib.pyplot as plt
from matplotlib.dates import DateFormatter, MinuteLocator, SecondLocator
import numpy as np
from StringIO import StringIO
import datetime as dt

### The example data
a=StringIO("""a 10:15:22 10:15:30 OK
b 10:15:23 10:15:28 OK
c 10:16:00 10:17:10 FAILED
b 10:16:30 10:16:50 OK
""")

#Converts str into a datetime object.
conv = lambda s: dt.datetime.strptime(s, '%H:%M:%S')

#Use numpy to read the data in. 
data = np.genfromtxt(a, converters={1: conv, 2: conv},
                     names=['caption', 'start', 'stop', 'state'], dtype=None)
cap, start, stop = data['caption'], data['start'], data['stop']

#Check the status, because we paint all lines with the same color 
#together
is_ok = (data['state'] == 'OK')
not_ok = np.logical_not(is_ok)

#Get unique captions and there indices and the inverse mapping
captions, unique_idx, caption_inv = np.unique(cap, 1, 1)

#Build y values from the number of unique captions.
y = (caption_inv + 1) / float(len(captions) + 1)

#Plot function
def timelines(y, xstart, xstop, color='b'):
    """Plot timelines at y from xstart to xstop with given color."""   
    plt.hlines(y, xstart, xstop, color, lw=4)
    plt.vlines(xstart, y+0.03, y-0.03, color, lw=2)
    plt.vlines(xstop, y+0.03, y-0.03, color, lw=2)

#Plot ok tl black    
timelines(y[is_ok], start[is_ok], stop[is_ok], 'k')
#Plot fail tl red
timelines(y[not_ok], start[not_ok], stop[not_ok], 'r')

#Setup the plot
ax = plt.gca()
ax.xaxis_date()
myFmt = DateFormatter('%H:%M:%S')
ax.xaxis.set_major_formatter(myFmt)
ax.xaxis.set_major_locator(SecondLocator(interval=20)) # used to be SecondLocator(0, interval=20)

#To adjust the xlimits a timedelta is needed.
delta = (stop.max() - start.min())/10

plt.yticks(y[unique_idx], captions)
plt.ylim(0,1)
plt.xlim(start.min()-delta, stop.max()+delta)
plt.xlabel('Time')
plt.show()

结果图


谢谢。我已经成功地使用您的解决方案绘制了一张图表。如果没有人提出更好的解决方案,我将接受您的答案。 - dm3
我更新了我的回答,我一直想学习matplotlib的日期功能。 - tillsten
1
对于不同的终止符号,您可以使用散点符号替换vlines。 plt.scatter(xstart,y,s = 100,c = color,marker ='x',lw = 2,edgecolor = color) - tillsten
2
这个例子在matplotlib 1.2(python 2.7,Fedora 19)上无法运行 - 代码似乎陷入了无限循环。 - maxschlepzig
在我的Mac OS 10.10上,使用matplotlib 1.4.0和Python 2.7可以正常工作。 - azalea

1

对于@tillsten的答案在Python3上不再起作用了,我进行了一些修改,希望能有所帮助。

import matplotlib.pyplot as plt
from matplotlib.dates import DateFormatter, MinuteLocator, SecondLocator
import numpy as np
import pandas as pd
import datetime as dt
import io

### The example data
a=io.StringIO("""
caption start stop state
a 10:15:22 10:15:30 OK
b 10:15:23 10:15:28 OK
c 10:16:00 10:17:10 FAILED
b 10:16:30 10:16:50 OK""")

data = pd.read_table(a, delimiter=" ")

data["start"] = pd.to_datetime(data["start"])
data["stop"] = pd.to_datetime(data["stop"])

cap, start, stop = data['caption'], data['start'], data['stop']

#Check the status, because we paint all lines with the same color 
#together
is_ok = (data['state'] == 'OK')
not_ok = np.logical_not(is_ok)

#Get unique captions and there indices and the inverse mapping
captions, unique_idx, caption_inv = np.unique(cap, 1, 1)

#Build y values from the number of unique captions.
y = (caption_inv + 1) / float(len(captions) + 1)

#Plot function
def timelines(y, xstart, xstop, color='b'):
    """Plot timelines at y from xstart to xstop with given color."""   
    plt.hlines(y, xstart, xstop, color, lw=4)
    plt.vlines(xstart, y+0.03, y-0.03, color, lw=2)
    plt.vlines(xstop, y+0.03, y-0.03, color, lw=2)

#Plot ok tl black    
timelines(y[is_ok], start[is_ok], stop[is_ok], 'k')
#Plot fail tl red
timelines(y[not_ok], start[not_ok], stop[not_ok], 'r')

#Setup the plot
ax = plt.gca()
ax.xaxis_date()
myFmt = DateFormatter('%H:%M:%S')
ax.xaxis.set_major_formatter(myFmt)
ax.xaxis.set_major_locator(SecondLocator(interval=20)) # used to be SecondLocator(0, interval=20)

#To adjust the xlimits a timedelta is needed.
delta = (stop.max() - start.min())/10

plt.yticks(y[unique_idx], captions)
plt.ylim(0,1)
plt.xlim(start.min()-delta, stop.max()+delta)
plt.xlabel('Time')
plt.show()

0

使用gnuplot 5.2版本创建唯一键列表

与@CiroSantilli的解决方案相比,主要区别在于从第1列自动创建唯一键列表,并且可以通过定义的函数Lookup()访问索引。引用的gnuplot演示已经使用了唯一项目列表,但是在OP的情况下存在重复项。

在gnuplot中创建这样的唯一项目列表并不存在,因此您必须自己实现它。该代码需要gnuplot >=5.2。可能很难获得适用于gnuplot 4.4(OP问题时)的解决方案,因为当时没有实现一些有用的功能:do for-loops、summation、数据块等(使用gnuplot 4.6的版本可能需要一些变通方法)。

编辑:早期版本使用with vectorslinewidth 20来绘制条形图,然而,linewidth 20也会在x方向上延伸,这里不是所需的。因此,现在使用with boxxyerror


是的,可以更短更清晰地完成。

脚本:

### Time chart with gnuplot (requires gnuplot>=5.0)
reset session

$Data <<EOD
# category        start      end        status
"event 1"         10:15:22   10:15:30   OK
"event 2"         10:15:23   10:15:28   OK
pause             10:16:00   10:17:10   FAILED
"something else"  10:16:30   10:17:50   OK
unknown           10:17:30   10:18:50   OK
"event 3"         10:18:30   10:19:50   FAILED
pause             10:19:30   10:20:50   OK
"event 1"         10:17:30   10:19:20   FAILED
EOD

# create list of unique items
uniqueList = ''
item(col)           = ' "'.strcol(col).'"'
isInList(list,col)  = strstrt(uniqueList,item(col))  # returns a number >0 if found
addToList(list,col) = list.item(col)
stats $Data u (!isInList(uniqueList,1) ? uniqueList = addToList(uniqueList,1) : 0) nooutput

timeCenter(col1,col2) = (timecolumn(col1,myTimeFmt)+timecolumn(col2,myTimeFmt))*0.5 
timeDeltaT(col1,col2) = (timecolumn(col1,myTimeFmt)-timecolumn(col2,myTimeFmt))*0.5 
Lookup(col)           = int(sum [i=1:words(uniqueList)] (strcol(col) eq word(uniqueList,i)) ? i : 0)
myColor(col)          = strcol(col) eq "OK" ? 0x00cc00 : 0xff0000
myBoxWidth            = 0.6

myTimeFmt = "%H:%M:%S"
set format x "%M:%S" timedate
set yrange [0.5:words(uniqueList)+0.5]
set grid x,y

plot $Data u (timeCenter(2,3)):(Lookup(1)):(timeDeltaT(2,3)):(0.5*myBoxWidth): \
             (myColor(4)):ytic(1) w boxxyerror fill solid 1.0 lc rgb var notitle
### end of script

结果:

enter image description here


-1

gnuplot with vector 解决方案

从以下链接中缩小:http://gnuplot.sourceforge.net/demo_5.2/gantt.html

main.gnuplot

#!/usr/bin/env gnuplot

$DATA << EOD
1 1 5
1 11 13
2 3 10
3 4 8
4 7 13
5 6 15
EOD

set terminal png size 512,512
set output "main.png"
set xrange [-1:]
set yrange [0:]
unset key
set border 3
set xtics nomirror
set ytics nomirror
set style arrow 1 nohead linewidth 3
plot $DATA using 2 : 1 : ($3-$2) : (0.0) with vector as 1, \
     $DATA using 2 : 1 : 1 with labels right offset -2

GitHub 上游

输出:

enter image description here

您可以通过删除第二个plot命令行来删除标签,我添加它们是因为在许多应用程序中它们非常有用,可以更轻松地识别间隔。

我链接的甘特图示例展示了如何处理日期格式而不是整数。

在gnuplot 5.2 patchlevel 2,Ubuntu 18.04中进行了测试。


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