Построение помеченных интервалов в matplotlib / gnuplot

#plot #matplotlib #gnuplot #intervals

#построение графика #matplotlib #gnuplot #интервалы

Вопрос:

У меня есть образец данных, который выглядит следующим образом:

 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/... ) могут повторяться, а могут и не повторяться.

Как я понял из документации для gnuplot и matplotlib, этот тип графика должен быть проще в последнем, поскольку это не стандартный график и потребует некоторой предварительной обработки.

Вопрос в том:

  1. Есть ли стандартный способ создания подобных графиков в любом из инструментов?
  2. Если нет, то как мне следует строить эти данные (указатели на соответствующие инструменты / документацию / функции / примеры, которые делают что-то вроде описанного здесь)?

Ответ №1:

Обновлено: теперь включает обработку выборки данных и использует функциональность даты 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()
  

Результирующее изображение

Комментарии:

1. Спасибо. Я успешно нарисовал график, используя ваше решение в качестве основы. Примет ваш ответ, если никто не предложит лучшего решения.

2. Я обновил свой ответ, я всегда хотел изучить функциональность даты matplotlibs.

3. Для разных конечных символов вы заменяете строки символами разброса. plt.scatter(xstart, y, s = 100, c = color, marker= ‘x’, lw = 2, edgecolor =цвет)

4. Этот пример не работает с matplotlib 1.2 (python 2.7, Fedora 19) — кажется, что код застрял в бесконечном цикле.

5. У меня работает с matplotlib 1.4.0 Python 2.7 в Mac OS 10.10.

Ответ №2:

ответ для @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()
  

Ответ №3:

версия gnuplot 5.2 с созданием уникального списка ключей

Основное отличие от решения @CiroSantilli заключается в том, что список уникальных ключей создается автоматически из столбца 1, а доступ к индексу можно получить с помощью определенной функции Lookup() . Демонстрационная версия gnuplot, на которую ссылается ссылка, уже использует список уникальных элементов, однако в случае с OP есть дубликаты.

Создание такого списка уникальных элементов не существует в gnuplot сразу, поэтому вы должны реализовать его самостоятельно. Для кода требуется gnuplot >=5.2. Вероятно, трудно получить решение, которое работает под gnuplot 4.4 (на момент вопроса OP), потому что в то время не было реализовано несколько полезных функций: do for циклы, summation , блоки данных, … (версия для gnuplot 4.6 может быть возможна с некоторыми обходными путями).

Редактировать: однако более ранняя версия, использовавшая with vectors и linewidth 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
  

Результат:

введите описание изображения здесь

Ответ №4:

решение 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.

Вывод:

введите описание изображения здесь

Вы можете удалить метки, удалив вторую plot командную строку, я добавил их, потому что они полезны во многих приложениях для более легкой идентификации интервалов.

Пример Ганта, на который я ссылался, показывает, как обрабатывать форматы даты вместо целых чисел.

Протестировано в gnuplot 5.2 patchlevel 2, Ubuntu 18.04.