I am interested in creating information about signal detections over time, much like what was done in this figure from Simonis et al. 2018 (Inter Research » MEPS » v577 » p221-235):
This figure with time on the x-axis and dates on the y-axis shows daily patterns in detections of feeding dolphins (the blue rectangles) with information about day/night (light gray shading indicates nighttime), lunar illumination during the night (the orange bands), and periods where there was no effort made to detect the animals (dark gray polygons).
I am interested in producing similar figures in dash/plotly. If I understand correctly, the Timeline class does not seem appropriate for this, and I was hoping that I could use the Heatmap class. I’ve written some preliminary code to do this and include a paired down version with hardcoded values at the end of this post. In it, I have a couple of sample periods of signals (e.g., dolphin detections), and a period that shows when we were not looking for the signal.
I used Heatmap with columns of dates and times where time was discretized to a duration (30 min). When I have only a few entries as in the example code, it becomes apparent that heatmap is creating boxes that are expanding to multiple days, even when I specify the xperiod and yperiod parameters. The 30 bins between midnight and 4 AM on Nov 16 are split on opposite sides of the x-axis which for some reason has gone beyond the 24 h, wrapping around to 4 AM.
I also tried to add a trace to this plot to show a region without analysis effort. The x-axis becomes furhter corrupted with times that are incongruent with the existing time axis. I am a novice dash/plotly user, but I am wondering if the two methods that I am using Heatmap and Scatter have different time representations. The figure below is the result of my Heatmap and Scatter:
I could manually draw each of the detection blocks as rectangles as I did for the gray region. However, I am hoping that there is something within plotly that might be more suitable. Any suggestions are welcome.
Thank you - Marie
# Demonstration of incongruent time axes when plotting with
# plotly graph_objects Scatter and Heatmap as well as Heatmap box interpolation
import plotly.graph_objects as go
import analysis.time_series
import pandas as pd
import datetime
# Data for showing presence of a signal over fixed intervals
# Will be discretized into bins of a given duration
bin = pd.Timedelta(minutes=30) # discretization duration
intervals = [ # UTC timestamps
"2017-11-04T00:00:15Z", "2017-11-04T01:45:00Z",
"2017-11-04T06:00:30Z", "2017-11-04T09:50:00Z",
"2017-11-15T19:25:58Z", "2017-11-16T03:50:00Z",
]
intervals = [pd.Timestamp(t) for t in intervals] # str -> Timestamp
# Create dataframe of times and values discretized by bin size
data = []
for idx in range(0, len(intervals), 2):
# Find range of bins that cover the interval
start = intervals[idx].floor(bin)
end = intervals[idx+1].ceil(bin)
current = start
while current <= end:
# Record presence (1) by date and time
data.append((current.date(), current.time(), 1))
# Also tried to use a duration instead of time without success:
# data.append((current.normalize(), current - current.normalize(), 1))
current = current + bin
# Merge into a dataframe for plotting
df = pd.DataFrame(data, columns=('Date', 'Time', 'Presence'))
"""
Contents of df :
Date Time Presence
0 2017-11-04 00:00:00 1 # Span between 11/4 00:00 - 02:00
1 2017-11-04 00:30:00 1
2 2017-11-04 01:00:00 1
3 2017-11-04 01:30:00 1
4 2017-11-04 02:00:00 1
5 2017-11-04 06:00:00 1 # Span between 11/4 06:00 - 10:00
6 2017-11-04 06:30:00 1
7 2017-11-04 07:00:00 1
8 2017-11-04 07:30:00 1
9 2017-11-04 08:00:00 1
10 2017-11-04 08:30:00 1
11 2017-11-04 09:00:00 1
12 2017-11-04 09:30:00 1
13 2017-11-04 10:00:00 1
14 2017-11-15 19:00:00 1 # Span between 11/15 19:00 - 11/16 04:00
15 2017-11-15 19:30:00 1
16 2017-11-15 20:00:00 1
17 2017-11-15 20:30:00 1
18 2017-11-15 21:00:00 1
19 2017-11-15 21:30:00 1
20 2017-11-15 22:00:00 1
21 2017-11-15 22:30:00 1
22 2017-11-15 23:00:00 1
23 2017-11-15 23:30:00 1
24 2017-11-16 00:00:00 1
25 2017-11-16 00:30:00 1
26 2017-11-16 01:00:00 1
27 2017-11-16 01:30:00 1
28 2017-11-16 02:00:00 1
29 2017-11-16 02:30:00 1
30 2017-11-16 03:00:00 1
31 2017-11-16 03:30:00 1
32 2017-11-16 04:00:00 1
"""
# Plot using a heatmap
fig = go.Figure(
# Plot bricks for the time and date
# Goal: color bricks by day to show which days and times have signal
# presence. In addition to below, tried zsmooth=None and connectgaps=False
go.Heatmap(x=df.Time, y=df.Date, z=df.Presence,
xperiod=bin.total_seconds()*1000.0,
yperiod=pd.Timedelta(days=1).total_seconds()*1000.0,
)
)
fig.update_xaxes(ticklabelmode="period")
fig.update_yaxes(ticklabelmode="period")
fig.show()
# These data are used to draw a polygonal region over areas where
# there was no effort to detect the signal
noeffort = [
"2017-11-01 04:30:19Z",
"2017-11-01 23:30:00Z",
"2017-11-02 23:30:00Z",
"2017-11-02 22:26:35Z",
"2017-11-03 22:26:35Z",
"2017-11-03 00:00:00Z",
"2017-11-02 00:00:00Z",
"2017-11-02 04:30:19Z",
]
noeffort = [pd.Timestamp(t) for t in noeffort]
fig.add_trace(
go.Scatter(x=[t.time() for t in noeffort],
y=[t.date() for t in noeffort],
line={'width': 0, 'color': 'grey'},
hovertext=f"No effort from {noeffort[0]} to {noeffort[-1]}",
hoverlabel_bgcolor="gray",
hoverlabel_font_color='white',
fill="toself",
name='no effort',
),
)
#xaxis={'range':[day_start, day_end]})
fig.show()