plotly / plotly/plotly.py

Unexpected behavior of rangebreaks with px.timeline and pattern "hour"

Aperta
#4,297 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

bug P3
Lingua principale
Python
Stelle
18.8k
Fork
2.8k
Merge medio
16h 26m
PR unite (30g)
21

Descrizione

Symptoms

When using rangebreaks for a plotly.express.timeline-figure with pattern='hour' and bounds=[17.4, 6.4] (i.e., exceeding midnight for excluding non-buisness-hours)
the resulting plot either misses all data (second plot) or data exceeding a certain span (fourth plot).

When doing the very same using values and dvalue kwords in the rangebreaks (in case of multiple days, one would need to provide a rangebreak for every day), the plots are generated as expected.

Environment:

# Name                    Version                   Build  Channel
_libgcc_mutex             0.1                 conda_forge    conda-forge
_openmp_mutex             4.5                       2_gnu    conda-forge
anyio                     3.7.1              pyhd8ed1ab_0    conda-forge
argon2-cffi               21.3.0             pyhd8ed1ab_0    conda-forge
argon2-cffi-bindings      21.2.0          py311hd4cff14_3    conda-forge
arrow                     1.2.3              pyhd8ed1ab_0    conda-forge
asttokens                 2.2.1              pyhd8ed1ab_0    conda-forge
async-lru                 2.0.3              pyhd8ed1ab_0    conda-forge
attrs                     23.1.0             pyh71513ae_1    conda-forge
babel                     2.12.1             pyhd8ed1ab_1    conda-forge
backcall                  0.2.0              pyh9f0ad1d_0    conda-forge
backports                 1.0                pyhd8ed1ab_3    conda-forge
backports.functools_lru_cache 1.6.5              pyhd8ed1ab_0    conda-forge
beautifulsoup4            4.12.2             pyha770c72_0    conda-forge
bleach                    6.0.0              pyhd8ed1ab_0    conda-forge
brotli-python             1.0.9           py311ha362b79_9    conda-forge
bzip2                     1.0.8                h7f98852_4    conda-forge
ca-certificates           2023.7.22            hbcca054_0    conda-forge
cached-property           1.5.2                hd8ed1ab_1    conda-forge
cached_property           1.5.2              pyha770c72_1    conda-forge
certifi                   2023.7.22          pyhd8ed1ab_0    conda-forge
cffi                      1.15.1          py311h409f033_3    conda-forge
charset-normalizer        3.2.0              pyhd8ed1ab_0    conda-forge
comm                      0.1.3              pyhd8ed1ab_0    conda-forge
debugpy                   1.6.7           py311hcafe171_0    conda-forge
decorator                 5.1.1              pyhd8ed1ab_0    conda-forge
defusedxml                0.7.1              pyhd8ed1ab_0    conda-forge
entrypoints               0.4                pyhd8ed1ab_0    conda-forge
exceptiongroup            1.1.2              pyhd8ed1ab_0    conda-forge
executing                 1.2.0              pyhd8ed1ab_0    conda-forge
flit-core                 3.9.0              pyhd8ed1ab_0    conda-forge
fqdn                      1.5.1              pyhd8ed1ab_0    conda-forge
idna                      3.4                pyhd8ed1ab_0    conda-forge
importlib-metadata        6.8.0              pyha770c72_0    conda-forge
importlib_metadata        6.8.0                hd8ed1ab_0    conda-forge
importlib_resources       6.0.0              pyhd8ed1ab_1    conda-forge
ipykernel                 6.25.0             pyh71e2992_0    conda-forge
ipython                   8.14.0             pyh41d4057_0    conda-forge
isoduration               20.11.0            pyhd8ed1ab_0    conda-forge
jedi                      0.18.2             pyhd8ed1ab_0    conda-forge
jinja2                    3.1.2              pyhd8ed1ab_1    conda-forge
json5                     0.9.14             pyhd8ed1ab_0    conda-forge
jsonpointer               2.0                        py_0    conda-forge
jsonschema                4.18.4             pyhd8ed1ab_0    conda-forge
jsonschema-specifications 2023.7.1           pyhd8ed1ab_0    conda-forge
jsonschema-with-format-nongpl 4.18.4             pyhd8ed1ab_0    conda-forge
jupyter-lsp               2.2.0              pyhd8ed1ab_0    conda-forge
jupyter_client            8.3.0              pyhd8ed1ab_0    conda-forge
jupyter_core              5.3.1           py311h38be061_0    conda-forge
jupyter_events            0.6.3              pyhd8ed1ab_1    conda-forge
jupyter_server            2.7.0              pyhd8ed1ab_0    conda-forge
jupyter_server_terminals  0.4.4              pyhd8ed1ab_1    conda-forge
jupyterlab                4.0.3              pyhd8ed1ab_0    conda-forge
jupyterlab_pygments       0.2.2              pyhd8ed1ab_0    conda-forge
jupyterlab_server         2.24.0             pyhd8ed1ab_0    conda-forge
kaleido                   0.2.1                    pypi_0    pypi
ld_impl_linux-64          2.40                 h41732ed_0    conda-forge
libblas                   3.9.0           17_linux64_openblas    conda-forge
libcblas                  3.9.0           17_linux64_openblas    conda-forge
libexpat                  2.5.0                hcb278e6_1    conda-forge
libffi                    3.4.2                h7f98852_5    conda-forge
libgcc-ng                 13.1.0               he5830b7_0    conda-forge
libgfortran-ng            13.1.0               h69a702a_0    conda-forge
libgfortran5              13.1.0               h15d22d2_0    conda-forge
libgomp                   13.1.0               he5830b7_0    conda-forge
liblapack                 3.9.0           17_linux64_openblas    conda-forge
libnsl                    2.0.0                h7f98852_0    conda-forge
libopenblas               0.3.23          pthreads_h80387f5_0    conda-forge
libsodium                 1.0.18               h36c2ea0_1    conda-forge
libsqlite                 3.42.0               h2797004_0    conda-forge
libstdcxx-ng              13.1.0               hfd8a6a1_0    conda-forge
libuuid                   2.38.1               h0b41bf4_0    conda-forge
libzlib                   1.2.13               hd590300_5    conda-forge
markupsafe                2.1.3           py311h459d7ec_0    conda-forge
matplotlib-inline         0.1.6              pyhd8ed1ab_0    conda-forge
mistune                   3.0.0              pyhd8ed1ab_0    conda-forge
nbclient                  0.8.0              pyhd8ed1ab_0    conda-forge
nbconvert-core            7.7.3              pyhd8ed1ab_0    conda-forge
nbformat                  5.9.1              pyhd8ed1ab_0    conda-forge
ncurses                   6.4                  hcb278e6_0    conda-forge
nest-asyncio              1.5.6              pyhd8ed1ab_0    conda-forge
notebook                  7.0.0              pyhd8ed1ab_0    conda-forge
notebook-shim             0.2.3              pyhd8ed1ab_0    conda-forge
numpy                     1.25.1          py311h64a7726_0    conda-forge
openssl                   3.1.1                hd590300_1    conda-forge
overrides                 7.3.1              pyhd8ed1ab_0    conda-forge
packaging                 23.1               pyhd8ed1ab_0    conda-forge
pandas                    2.0.3           py311h320fe9a_1    conda-forge
pandocfilters             1.5.0              pyhd8ed1ab_0    conda-forge
parso                     0.8.3              pyhd8ed1ab_0    conda-forge
pexpect                   4.8.0              pyh1a96a4e_2    conda-forge
pickleshare               0.7.5                   py_1003    conda-forge
pip                       23.2.1             pyhd8ed1ab_0    conda-forge
pkgutil-resolve-name      1.3.10             pyhd8ed1ab_0    conda-forge
platformdirs              3.9.1              pyhd8ed1ab_0    conda-forge
plotly                    5.15.0             pyhd8ed1ab_0    conda-forge
prometheus_client         0.17.1             pyhd8ed1ab_0    conda-forge
prompt-toolkit            3.0.39             pyha770c72_0    conda-forge
prompt_toolkit            3.0.39               hd8ed1ab_0    conda-forge
psutil                    5.9.5           py311h2582759_0    conda-forge
ptyprocess                0.7.0              pyhd3deb0d_0    conda-forge
pure_eval                 0.2.2              pyhd8ed1ab_0    conda-forge
pycparser                 2.21               pyhd8ed1ab_0    conda-forge
pygments                  2.15.1             pyhd8ed1ab_0    conda-forge
pysocks                   1.7.1              pyha2e5f31_6    conda-forge
python                    3.11.4          hab00c5b_0_cpython    conda-forge
python-dateutil           2.8.2              pyhd8ed1ab_0    conda-forge
python-fastjsonschema     2.18.0             pyhd8ed1ab_0    conda-forge
python-json-logger        2.0.7              pyhd8ed1ab_0    conda-forge
python-tzdata             2023.3             pyhd8ed1ab_0    conda-forge
python_abi                3.11                    3_cp311    conda-forge
pytz                      2023.3             pyhd8ed1ab_0    conda-forge
pyyaml                    6.0             py311hd4cff14_5    conda-forge
pyzmq                     25.1.0          py311h75c88c4_0    conda-forge
readline                  8.2                  h8228510_1    conda-forge
referencing               0.30.0             pyhd8ed1ab_0    conda-forge
requests                  2.31.0             pyhd8ed1ab_0    conda-forge
rfc3339-validator         0.1.4              pyhd8ed1ab_0    conda-forge
rfc3986-validator         0.1.1              pyh9f0ad1d_0    conda-forge
rpds-py                   0.9.2           py311h46250e7_0    conda-forge
send2trash                1.8.2              pyh41d4057_0    conda-forge
setuptools                68.0.0             pyhd8ed1ab_0    conda-forge
six                       1.16.0             pyh6c4a22f_0    conda-forge
sniffio                   1.3.0              pyhd8ed1ab_0    conda-forge
soupsieve                 2.3.2.post1        pyhd8ed1ab_0    conda-forge
stack_data                0.6.2              pyhd8ed1ab_0    conda-forge
tenacity                  8.2.2              pyhd8ed1ab_0    conda-forge
terminado                 0.17.1             pyh41d4057_0    conda-forge
tinycss2                  1.2.1              pyhd8ed1ab_0    conda-forge
tk                        8.6.12               h27826a3_0    conda-forge
tomli                     2.0.1              pyhd8ed1ab_0    conda-forge
tornado                   6.3.2           py311h459d7ec_0    conda-forge
traitlets                 5.9.0              pyhd8ed1ab_0    conda-forge
typing-extensions         4.7.1                hd8ed1ab_0    conda-forge
typing_extensions         4.7.1              pyha770c72_0    conda-forge
typing_utils              0.1.0              pyhd8ed1ab_0    conda-forge
tzdata                    2023c                h71feb2d_0    conda-forge
uri-template              1.3.0              pyhd8ed1ab_0    conda-forge
urllib3                   2.0.4              pyhd8ed1ab_0    conda-forge
wcwidth                   0.2.6              pyhd8ed1ab_0    conda-forge
webcolors                 1.13               pyhd8ed1ab_0    conda-forge
webencodings              0.5.1                      py_1    conda-forge
websocket-client          1.6.1              pyhd8ed1ab_0    conda-forge
wheel                     0.41.0             pyhd8ed1ab_0    conda-forge
xz                        5.2.6                h166bdaf_0    conda-forge
yaml                      0.2.5                h7f98852_2    conda-forge
zeromq                    4.3.4                h9c3ff4c_1    conda-forge
zipp                      3.16.2             pyhd8ed1ab_0    conda-forge

MWE:

import datetime

import pandas as pd
import plotly.express as px

import plotly.io as pio

pio.renderers.default = "svg"

# build example data
df = pd.read_json(
    """
    {
    "start":{
        "0":"2023-07-27T10:54:28.000Z",
        "1":"2023-07-27T11:40:15.000Z",
        "2":"2023-07-27T15:00:58.000Z",
        "3":"2023-07-27T11:52:28.000Z",
        "4":"2023-07-27T12:52:57.000Z",
        "5":"2023-07-27T13:20:45.000Z",
        "6":"2023-07-27T13:44:24.000Z"
    },
    "end":{
        "0":"2023-07-27T10:54:28.000Z",
        "1":"2023-07-27T11:53:44.000Z",
        "2":"2023-07-27T15:06:50.000Z",
        "3":"2023-07-27T11:55:19.000Z",
        "4":"2023-07-27T13:01:35.000Z",
        "5":"2023-07-27T13:23:10.000Z",
        "6":"2023-07-27T13:47:03.000Z"
    },
    "device_number":{
        "0":"168012",
        "1":"168012",
        "2":"168012",
        "3":"202052",
        "4":"202052",
        "5":"202052",
        "6":"202052"
    }
}

    """,
    dtype=dict(device_number=str)
)

# code for a workaround using value/dvalue rangebreaks for every date in a (known) xrange
df[['start', 'end']] = df[['start', 'end']].apply(lambda x: pd.to_datetime(x).astype('datetime64[ns, UTC]'))

dt_interval = (datetime.datetime(2023, 7, 27, 0, 0, 0, 0), datetime.datetime(2023, 7, 27, 23, 59, 59, 99999))
restrict_timeinterval = (datetime.time(6, 24, 0, 0), datetime.time(17, 24, 0, 0))
no_days_in_range_x = (dt_interval[1].date() - dt_interval[0].date()).days + 1
date_list = [(dt_interval[0].date() + datetime.timedelta(days=k))
             for k in range(no_days_in_range_x)] if no_days_in_range_x > 1 else [dt_interval[0].date()]
dvalue0 = datetime.datetime.combine(datetime.date.today(), restrict_timeinterval[0]) \
          - datetime.datetime.combine(datetime.date.today(), datetime.time.min)
dvalue1 = datetime.datetime.combine(datetime.date.today(), datetime.time.max) \
          - datetime.datetime.combine(datetime.date.today(), restrict_timeinterval[1])
values0 = [datetime.datetime.combine(dt, tm)
           for dt, tm in zip(date_list, (datetime.time.min,) * no_days_in_range_x)]
values1 = [datetime.datetime.combine(dt, tm)
           for dt, tm in zip(date_list, (restrict_timeinterval[1],) * no_days_in_range_x)]
print(dvalue0, dvalue1)
print(values0, values1)
print(df.values)

# define different rangebreaks to test
l_rangebreaks_default = []
l_rangebreaks_pattern_0 = [
    #dict(values=values0, dvalue=dvalue0.total_seconds() * 1e3),
     dict(bounds=[17.4, 24], pattern='hour'),
     dict(bounds=[0, 6.4], pattern='hour'),
]
l_rangebreaks_pattern_1 = [
    #dict(values=values0, dvalue=dvalue0.total_seconds() * 1e3),
     dict(bounds=[17.4, 24], pattern='hour'),
     dict(bounds=[0.2, 6.4], pattern='hour'),
]
l_rangebreaks_values_0 = [
    dict(values=values0, dvalue=dvalue0.total_seconds() * 1e3),
    dict(values=values1, dvalue=dvalue1.total_seconds() * 1e3),
]

# build plotly timelines
for l_rangebreaks in [
    l_rangebreaks_default,
    l_rangebreaks_pattern_0,
    l_rangebreaks_pattern_1,
    l_rangebreaks_values_0,
]:
    print(l_rangebreaks)
    fig = px.timeline(
        data_frame=df,
        x_start='start',
        x_end='end',
        y='device_number',
        range_x=dt_interval,
        color_continuous_scale=px.colors.sequential.Rainbow,
        color='device_number',
    )
    fig.update_xaxes(
        rangebreaks=l_rangebreaks
    )
    fig.show(renderer='svg')

Output

6:24:00 6:35:59.999999
[datetime.datetime(2023, 7, 27, 0, 0)] [datetime.datetime(2023, 7, 27, 17, 24)]
[[Timestamp('2023-07-27 10:54:28+0000', tz='UTC')
  Timestamp('2023-07-27 10:54:28+0000', tz='UTC') '168012']
 [Timestamp('2023-07-27 11:40:15+0000', tz='UTC')
  Timestamp('2023-07-27 11:53:44+0000', tz='UTC') '168012']
 [Timestamp('2023-07-27 15:00:58+0000', tz='UTC')
  Timestamp('2023-07-27 15:06:50+0000', tz='UTC') '168012']
 [Timestamp('2023-07-27 11:52:28+0000', tz='UTC')
  Timestamp('2023-07-27 11:55:19+0000', tz='UTC') '202052']
 [Timestamp('2023-07-27 12:52:57+0000', tz='UTC')
  Timestamp('2023-07-27 13:01:35+0000', tz='UTC') '202052']
 [Timestamp('2023-07-27 13:20:45+0000', tz='UTC')
  Timestamp('2023-07-27 13:23:10+0000', tz='UTC') '202052']
 [Timestamp('2023-07-27 13:44:24+0000', tz='UTC')
  Timestamp('2023-07-27 13:47:03+0000', tz='UTC') '202052']]

Plots

No rangebreaks
[]

output_0_1

Fails
[{'bounds': [17.4, 24], 'pattern': 'hour'}, {'bounds': [0, 6.4], 'pattern': 'hour'}]

output_0_3

[{'bounds': [17.4, 24], 'pattern': 'hour'}, {'bounds': [0.2, 6.4], 'pattern': 'hour'}]
Fails: note the vanishing timeslot on the upper device!

output_0_5

Workaround works as expected.
[{'values': [datetime.datetime(2023, 7, 27, 0, 0)], 'dvalue': 23040000.0}, {'values': [datetime.datetime(2023, 7, 27, 17, 24)], 'dvalue': 23759999.998999998}]

output_0_7

Other matters

I assume it has something to do how x_start and x_end are internally translated to base and x for px.bar/px.timeline...

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Inizia eseguendo il MWE Python fornito con pandas e Plotly Express, confrontando i rangebreaks che usano pattern="hour" e bounds=[17.4, 6.4] con le forme values e dvalue. Traccia il modo in cui la figura timeline applica questi rangebreaks e conferma che il comportamento corretto preservi tutti i dati attraverso la mezzanotte e su più giorni.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
pandas, python
Ambito
data-visualization
Tipo di issue
Bug
Difficoltà
3/5
Tempo stimato
1-2 giorni
Stato di attività
Ferma
Chiarezza
Abbastanza chiara
Idoneità per principianti
48/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.