3

I recently created a plot using ipywidgets. The goal was to display data in a stacked barplot and to have two dropdown menus to select the start and end month for the plot, each bar representing data for one month. I was pretty happy with the result since everything worked until I restarted my colab notebook two days after. Now running the code first returns the correct output with the two dropdown menus but the selection of either month results in a new plot, being created underneath the first one.

I spent a lot of time trying to fix it, using clear_output() in multiple ways, creating the matplotlib figure outside the function, and restarting the kernel but I just couldn't change the way it behaves. Does anyone have a solution to this?

I am new to ipywidgets, so if my approach is fundamentally incorrect, please don't hesitate to guide me in a more promising direction.

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import datetime as dt
import ipywidgets as widgets
from IPython.display import display
from IPython.display import clear_output

def minute(pd_datetime):
  return int(pd_datetime.total_seconds() / 60)

#This data is created just for the sake of a minimal example and I hope this works:
start_limit=pd.to_datetime("2019-09-01",utc=True)
end_limit=pd.to_datetime("2023-05-01",utc=True)
num_minutes = int((end_limit - start_limit).total_seconds() / 60)

numbers = numbers = np.random.randint(0, 100, size=(num_minutes, 1), dtype=np.uint16)
time = np.array([start_limit + dt.timedelta(minutes=i) for i in range(numbers.shape[0])])
numbers_df = pd.DataFrame(numbers)
numbers_df.index = time

output = widgets.Output()
clear_output()

# define function to update plot based on selected months
def update_plot_monthly_usage(start_limit, end_limit, min_month, max_month):
  clear_output(wait=True)

  fig1=plt.figure(figsize=(10,6))
  ax1=fig1.add_subplot(111)
  # convert selected months to start and end timestamps
  start = pd.to_datetime(min_month + '-01',utc=True)
  end = pd.to_datetime(max_month + '-01',utc=True) + pd.offsets.MonthEnd(1)


  # filter data based on selected months
  ind1 = max(minute(start-start_limit),0)
  ind2 = min(int((end-start_limit).total_seconds()/60),int((end_limit-start_limit).total_seconds()/60))
  
  # group data by month and sum
  
 
  monthly_data = numbers_df.iloc[ind1:ind2].groupby(pd.Grouper(freq='M'))
  months=monthly_data.ngroups
  monthly_data = monthly_data.sum()/60 #hours

  
  # plot the data in the subplot

  if months>=10:
    ax1.bar(monthly_data.sum(axis=1).index, monthly_data.sum(axis=1),width=20) #Purely cosmetic
  else:
    ax1.bar(pd.date_range(start=min_month+'-01', end=max_month+'-01', freq='MS').strftime('%Y-%m'), monthly_data.sum(axis=1))
  
  ax1.set_xlabel('Month')
  ax1.set_ylabel('Total usage in hours')
  ax1.set_title('Platform usage for selected months')


# create dropdown widgets for selecting months
min_month = widgets.Dropdown(options=pd.date_range(start='2019-09-01', end='2023-01-01', freq='MS').strftime('%Y-%m'), description='Min Month')
max_month = widgets.Dropdown(options=pd.date_range(start='2019-10-01', end='2023-02-01', freq='MS').strftime('%Y-%m'), description='Max Month')

# create interactive plot
widgets.interact(update_plot_monthly_usage, start_limit=widgets.fixed(start_limit), end_limit=widgets.fixed(end_limit), min_month=min_month, max_month=max_month)
display(output)

In the colab notebook, I have several other plots that work pretty much the same, it was also a question if I need to change any of the names in order to run the whole notebook, as I have to run every cell separately at the moment, for it to display anything. I suspected the reason was the widgets "min_month" and "max_month" having the same name in all of the cells but this is a side question of minor importance. I excluded the stacked barplots here.

As said above, I tried making it work using clear_output(), it however doesn't clear the output. Equally clear_output() doesn't work if used outside of the interact function, at the end of the code. Another suggestion I followed was putting the whole function into a "with output:" statement but this didn't change any of the behavior so I excluded it.

j42
  • 31
  • 3
  • I have confirmed this behavior in Google Colab. The given code correctly updates the given figure when using jupyter lab. – Trenton McKinney Apr 24 '23 at 19:06
  • This is a new bug. I have used interactive successfully in a notebook for 5 years until this unexpected behavior started. – MrIO Apr 28 '23 at 09:38
  • Same issue, though it doesn't work with Jupyter Lab either. Jupyter Lab and Dataspell don't update the plot at all. Colab at least produces some output. – matfax May 07 '23 at 12:54
  • Juypter Notebook with ipympl behaves just like Colab. – matfax May 07 '23 at 13:32

2 Answers2

0

I was having the same issue yesterday and found a solution in an example notebook for ipywidgets on the section about update flickering. I noticed that there was a plt.show() command at the end of the update function, and adding that too my code was enough to prevent the continual addition of new plots. I tested it on the code you posted as well and it seems to sort things out there too.

VDB
  • 1
  • 1
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/late-answers/34607382) – Koedlt Jul 01 '23 at 07:41
0

The solution is same as the one suggested by @VDB but for the sake of completness I am adding an example, from the link suggested in the answer.

%matplotlib inline
from ipywidgets import interactive
import matplotlib.pyplot as plt
import numpy as np

def f(m, b):
    plt.figure(2)
    x = np.linspace(-10, 10, num=1000)
    plt.plot(x, m * x + b)
    plt.ylim(-5, 5)
    plt.show() # This is part that needs to be added to fix the issue

interactive_plot = interactive(f, m=(-2.0, 2.0), b=(-3, 3, 0.5))
output = interactive_plot.children[-1]
output.layout.height = '350px'
interactive_plot