4

I'm trying to plot a binary timeline using matplotlib (I might be able to consider alternative libraries, though).

Now, by "binary timeline" I mean the "display of chronological events, where the event space is made of two opposite events". An example of such an event space could be {no_one_in_the_team_is_sick, at_least_one_person_in_the_team_is_sick}.

The representation I'd like to replicate is this (I did it using d3): enter image description here

I've tried exploring the use of stacked horizontal bars, but it's clearly not the right tool for the job.

Is there an easier and/or more correct way of achieving that result?

Suh Fangmbeng
  • 573
  • 4
  • 16
Jir
  • 2,985
  • 8
  • 44
  • 66
  • Could you provide a small amount of sample data ? – jadsq Jul 06 '17 at 15:16
  • You may also look at [this question](https://stackoverflow.com/questions/32619424/is-it-possible-to-plot-timelines-with-matplotlib?rq=1), which gives some possible, but not perferct solutions. – ImportanceOfBeingErnest Jul 06 '17 at 15:50
  • @jadsq a minimal example could be `[(391030.0, True), (63202.0, False), (150568.0, True)]` where the number in each tuple is the width of the box and the second is the binary event. @ImportanceOfBeingErnest the solution using `scatter` is neat, but the main drawback is that it displays points, so there will be empty gaps (unless I normalize all data to the gcd of all widths, which I'd like to avoid); the solution using PIL is also nice, but seems overkill for the moment. – Jir Jul 07 '17 at 07:23

3 Answers3

7

You may use broken_barhto plot a binary timeline.

enter image description here

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates

#create a time series s with dates as index and 0 and 1 for events
dates = pd.date_range("2017-04-01","2017-06-15", freq="D")
events = np.random.random_integers(0,1,size=len(dates))
s = pd.Series(events, index=dates)

fig, ax= plt.subplots(figsize=(6,2))

# plot green for event==1
s1 = s[s == 1]
inxval = matplotlib.dates.date2num(s1.index.to_pydatetime())
times= zip(inxval, np.ones(len(s1)))
plt.broken_barh(times, (-1,1), color="green")
# plot red for event==0
s2 = s[s == 0]
inxval = matplotlib.dates.date2num(s2.index.to_pydatetime())
times= zip(inxval, np.ones(len(s2)))
plt.broken_barh(times, (-1,1), color="red")

#format axes
ax.margins(0)
ax.set_yticks([])
ax.xaxis.set_major_locator(matplotlib.dates.MonthLocator())
ax.xaxis.set_minor_locator(matplotlib.dates.DayLocator())
monthFmt = matplotlib.dates.DateFormatter("%b")
ax.xaxis.set_major_formatter(monthFmt)
plt.tight_layout()
plt.show()
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • Though I skimmed through matplotlib's examples I didn't spot `broken_barh`. Thanks for pointing it out - I believe I can adapt it to my needs! – Jir Jul 07 '17 at 07:15
  • 1
    In case someone has to transform `timedelta64` to floats in order to plot the box with a given width (instead of using `np.ones` as in the example above), what I've resorted to - assuming you've got a DataFrame with a "duration" column - is `df.duration.map(pd.Timedelta.to_pytimedelta) / np.timedelta64(1, 'D')`. This basically returns the duration in days, similar to what `date2num` does for dates. – Jir Jul 07 '17 at 08:41
3

This may be useful to you:

Rich Matplotlib timeline visualization

It does display much richer information than you might need though.

enter image description here

Raul
  • 701
  • 7
  • 6
0

Just to go off of ImportanceOfBeingErnest's solution, I had to hardcode times to a list, as a zip object is an iterator and was throwing errors as is.

Ex. plt.broken_barh(list(times), (-1,1), color="green")