Issue with visualizing air raid siren duration on the plot

Viewed 40

I try to visualize the duration of air raid sirens in my country by date and time. There is one problem I'm struggling with. When the air siren starts one day and ends the next day it fills up the entire day of the plot.
E.g. air siren started on Mar 2, 23:21 (11:21 pm) and ended on Mar 3, 00:38 then the entire day would be filled out as you can see on the screenshot.

My code

    alt.data_transformers.disable_max_rows()
    
    dropdown = alt.binding_select(options=df.region.unique(), name='Select Region')
    select_region = alt.selection_single(empty='none', bind=dropdown, fields=['region'], init={'region':'Київ'})
    
    layer1 = alt.Chart(df).mark_rect().encode(
        y=alt.Y('hours(siren_start_at):O', title='hour of day'
                ,axis=alt.Axis(format='%H:%M', grid=True)
                ,scale=alt.Scale(reverse=True)
                ),
        y2=alt.Y2('hours(siren_end_at):O'),
        x=alt.X('monthdate(siren_start_at):O', title='date', axis = alt.Axis(grid=True)),
        tooltip = [
                   alt.Tooltip('monthdate(siren_start_date):O', title='siren started date')
                   ,alt.Tooltip('hoursminutes(siren_start_at):O', title='siren started at')
                   ,alt.Tooltip('monthdate(siren_end_date):O', title='siren end date')
                   ,alt.Tooltip('hoursminutes(siren_end_at):O', title='siren ended at')
                   ,alt.Tooltip('duration:Q', title='siren duration (min)')
        ]
    ).add_selection(select_region).transform_filter(select_region) 
    
    layer2 = alt.Chart(df).mark_rect().encode(
        x=alt.X('monthdate(siren_start_at):O', title='date', axis = alt.Axis(orient='top', grid=True))
        ,y=alt.Y('hours(siren_start_at):O', title='hour of day'
                ,axis=alt.Axis(format='%H:%M', grid=True)
                ,scale=alt.Scale(reverse=True)
                )
        ,y2=alt.Y2('hours(siren_end_at):O')    
    ).transform_filter(select_region) 
    
    alt.layer(layer1, layer2).resolve_scale(
        x = 'independent'
    )

Air sirens duration by date and time

My data frame is stored here https://github.com/GendolfJunior/UA_air_sirens.

The full dataset is available here https://github.com/Vadimkin/ukrainian-air-raid-sirens-dataset. Credits to @Vadimkin.

1 Answers

Since you are using only the hours on the y-axis, there is no way for Altair to know that 00:00 the next day comes after 23:00, and it will draw the rectangle between 0 and 23 on the same day. I don't think there is a more elegant way around this than to write a function to split all the dates that span midnight into two separate entries, one ending at 23:59:59 and the other starting at 00:00:00 the next day. The relevant rows would be the ones matching the condition (pd.to_datetime(df['siren_start_at']).dt.day - pd.to_datetime(df['siren_end_at']).dt.day) != 0.

Another approach is to think about different ways of visualizing the same data. E.g. you could show just the start time and then use either color or size of the marker to indicate the duration:

dropdown = alt.binding_select(options=df.region.unique(), name='Select Region ')
select_region = alt.selection_single(empty='none', bind=dropdown, fields=['region'], init={'region':'Київ'})

alt.Chart(df).mark_rect().encode(
    y=alt.Y(
        'hours(siren_start_at):T',
        title='Start hour of day',
        axis=alt.Axis(format='%H:%M', grid=True),
        scale=alt.Scale(reverse=True),
    ),
    color='duration',
    x=alt.X('monthdate(siren_start_at):O', title='', axis=alt.Axis(grid=True)),
    tooltip = [
        alt.Tooltip('monthdate(siren_start_date):O', title='siren started date'),
        alt.Tooltip('hoursminutes(siren_start_at):O', title='siren started at'),
        alt.Tooltip('monthdate(siren_end_date):O', title='siren end date'),
        alt.Tooltip('hoursminutes(siren_end_at):O', title='siren ended at'),
        alt.Tooltip('duration:Q', title='siren duration (min)'),
    ]
).add_selection(
    select_region
).transform_filter(
    select_region
)

enter image description here

Or the size of a circle:

dropdown = alt.binding_select(options=df.region.unique(), name='Select Region ')
select_region = alt.selection_single(empty='none', bind=dropdown, fields=['region'], init={'region':'Київ'})

alt.Chart(df, width=800).mark_circle().encode(
    y=alt.Y(
        'hours(siren_start_at):T',
        title='Start hour of day',
        axis=alt.Axis(format='%H:%M', grid=True),
        scale=alt.Scale(reverse=True),
    ),
    size='duration',
    x=alt.X('monthdate(siren_start_at):T', title='', axis=alt.Axis(grid=True)),
    tooltip = [
        alt.Tooltip('monthdate(siren_start_date):O', title='siren started date'),
        alt.Tooltip('hoursminutes(siren_start_at):O', title='siren started at'),
        alt.Tooltip('monthdate(siren_end_date):O', title='siren end date'),
        alt.Tooltip('hoursminutes(siren_end_at):O', title='siren ended at'),
        alt.Tooltip('duration:Q', title='siren duration (min)'),
    ]
).add_selection(
    select_region
).transform_filter(
    select_region
)

enter image description here

You could also do something like this to get an overview of which regions are similar in their siren duration pattern:

alt.Chart(df, width=1800).mark_rect().encode(
    y=alt.Y('region', sort=df.groupby('region')['duration'].sum().sort_values(ascending=False).index.to_list()),
    tooltip = [
               alt.Tooltip('monthdate(siren_start_date):O', title='siren started date')
               ,alt.Tooltip('hoursminutes(siren_start_at):O', title='siren started at')
               ,alt.Tooltip('monthdate(siren_end_date):O', title='siren end date')
               ,alt.Tooltip('hoursminutes(siren_end_at):O', title='siren ended at')
               ,alt.Tooltip('duration:Q', title='siren duration (min)')
    ],
    x=alt.X('siren_start_at:T', title='date', axis = alt.Axis(grid=True)),
    x2=alt.X2('siren_end_at:T'),
)

enter image description here

Related