plotly.py icon indicating copy to clipboard operation
plotly.py copied to clipboard

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Open willgoldby opened this issue 2 years ago • 8 comments

https://github.com/plotly/plotly.py/blob/956ab2cd158f0d20b322aa49e2f717a56aa6b9e2/packages/python/plotly/plotly/basewidget.py#L857

I'm getting the following error from line 857 in basewidget.py:

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

willgoldby avatar Apr 14 '23 20:04 willgoldby

We're going to need some more context to be able to help you out at all here :) can you provide some minimal test case that shows how you're using the library such that this happens?

nicolaskruchten avatar Apr 14 '23 23:04 nicolaskruchten

Thanks. Sorry for the late reply.

Here's the code to run in a Google Colab.

From the dropdown menu for "Add car data," select "car data." The error should then occur.

import pandas as pd
import numpy as np
import plotly.graph_objects as go
from traitlets.config.application import deepcopy
from google.colab import output
output.enable_custom_widget_manager()
import datetime
from ipywidgets import widgets
import math
import random
from IPython.display import display

global car_weight_mpg

# Data points
car_weight_mpg = pd.DataFrame({
    'x': [3.5, 3.69, 3.44, 3.43, 3.45, 4.34, 4.35, 4.31, 4.42, 3.85, 3.56, 3.61,
      3.76, 3.09, 2.37, 2.83, 2.77, 2.59, 2.13, 1.83, 2.67, 2.43, 2.38, 2.23,
      2.65, 4.62, 4.38, 4.38, 4.73, 2.13, 2.26, 2.23, 2.05, 2.63, 3.44, 3.33,
      3.3, 3.29, 4.21, 4.46, 4.15, 4.1, 4.96, 4.75, 5.14, 2.96, 2.41, 3.28,
      3.14, 2.22, 2.12, 2.07, 2.06, 1.77, 1.61, 1.83, 1.96, 2.28, 2.13, 2.25,
      2.41, 2.23, 4.27, 4.38, 4.13, 4.13, 3.67, 4.63, 4.5, 4.46, 4.42, 2.33,],


    'y': [18.0, 15.0, 18.0, 16.0, 17.0, 15.0, 14.0, 14.0, 14.0, 15.0, 15.0, 14.0,
     15.0, 14.0, 24.0, 22.0, 18.0, 21.0, 27.0, 26.0, 25.0, 24.0, 25.0, 26.0,
     21.0, 10.0, 10.0, 11.0, 9.0, 27.0, 28.0, 25.0, 25.0, 19.0, 16.0, 17.0,
     19.0, 18.0, 14.0, 14.0, 14.0, 14.0, 12.0, 13.0, 13.0, 18.0, 22.0, 19.0,
     18.0, 23.0, 28.0, 30.0, 30.0, 31.0, 35.0, 27.0, 26.0, 24.0, 25.0, 23.0,
     20.0, 21.0, 13.0, 14.0, 15.0, 14.0, 17.0, 11.0, 13.0, 12.0, 13.0, 19.0,]})



# global variables
global points
global graph_objects

# figure to put all points and traces onto
ffigg = go.FigureWidget(data=(),
                              layout=go.Layout(title=dict(text="Loss"),
                                                # xaxis=dict(range=[0,3]),
                                                # yaxis=dict(range=[0,50]),
                                                width=1000,
                                                height=500))

# Get noise
noise = widgets.Dropdown(
    options= [('No data', -1), ('Car data',1)],
    value = -1,
    description='Add car data',
    disabled=False,
)



def get_mse():
  values = loss_trace_points.values()
  values = list(values)

  single_values = []

  for i in range(len(values)):
    single_values.append(values[i][1])

  squared_values = []
  for l in single_values:
    squared = (l[0] - l[1])**2
    squared_values.append(squared)



  result = round((sum(squared_values)/len(single_values)),2)
  return result

weight = widgets.BoundedFloatText(
    value=0,
    min=-100,
    max=100,
    step=.1,
    description='Weight:',
    disabled=False
    )

bias = widgets.BoundedFloatText(
    value=0,
    min=-100,
    max=100,
    step=.1,
    description='Bias:',
    disabled=False
    )

loss_lines_on = widgets.Checkbox(
    value=False,
    description='Turn loss lines on',
    disabled=False,
    indent=False
)

mse = widgets.Text(
    value='None',
    description='MSE',
    disabled=False,
)

# Get loss trace points when regression line moves
def get_loss_trace_points(points):
  global loss_trace_points
  loss_trace_points = {}
  with ffigg.batch_update():
    for i in range(len(points['x'])):
      loss_trace_points['loss_trace_' + str(i)] = [
                                              [points['x'][i], points['x'][i]],
                                              [points['y'][i], new_weight*points['x'][i]+new_bias]
                                              ]

  return loss_trace_points


def create_loss_traces(change):
  global loss_trace_points
  # Turn loss traces off
  try:
    with ffigg.batch_update():
      if (loss_lines_on.get_interact_value() == False):
        for i in range(len(loss_trace_points)):
          ffigg.update_traces(selector=dict(name='loss_trace_' + str(i)), visible=False)

        # Turn widget data off if traces are off
        mse.value = 'None'

      ###
      ## Create loss traces
      ###
      elif (loss_lines_on.get_interact_value()== True):
        loss_trace_points = {}
        with ffigg.batch_update():
          # Get loss points
          for i in range(len(points['x'])):
            loss_trace_points['loss_trace_' + str(i)] = [
                                                  [points['x'][i], points['x'][i]],
                                                  [points['y'][i], weight.get_interact_value()*points['x'][i]+bias.get_interact_value()]
                                                  ]

          global loss_traces
          loss_traces = {}
          # Add loss traces to graph
          for i in range(len(loss_trace_points)):
            ffigg.add_trace(go.Scatter(x=loss_trace_points['loss_trace_' + str(i)][0],
                                            y=loss_trace_points['loss_trace_'+ str(i)][1],
                                            mode='lines+text',
                                            showlegend=False,
                                            name='loss_trace_' + str(i), line_color="#53adcb"))

          # Add initial loss data to widget
          mse.value = "{:.2f}".format(get_mse())

  except Exception as e:
    print(e)
    pass


def add_data_points(change):

  global points

  # Clear figure data so traces are cleared each time
  if (len(ffigg.data) != 0):
    ffigg.data = ()

  # Data when change is Zero
  change_value = change['owner'].__dict__['_trait_values']['value']
  if (type(change['owner']) == widgets.widget_selection.Dropdown):

    # Clear data when "No Data" is selected
    if (change_value == -1):
      ffigg.data = ()
      points = pd.DataFrame({'x':[], 'y': []})
     
    # Data when change is car data
    elif (change_value == 1):
      ffigg.data = ()
      # Car data
      points = car_weight_mpg

    else:
      print("Turn trace lines on and off")

  # Add points to loss_figure
  with ffigg.batch_update():
    ffigg.add_trace(go.Scatter(x=points['x'], y=points['y'], mode="markers",
                                    name='Data', marker_size=12, marker_color='#ff726f'))


  # Draw regression line
  initial_weight = weight.get_interact_value()
  initial_bias = bias.get_interact_value()
  x_as_list = list(points['x'])
  y_as_list = list(points['y'])

  # Regression line data
  # Get smallesst x and y to graph first point for regression line.
  if (change_value != -1): # If there is any data
    min_x = min(x_as_list)
    max_x = max(x_as_list)
    min_y = min(y_as_list)
    max_y = max(y_as_list)

    regression_line_data = pd.DataFrame({'x': [min_x, max_x],
                                       'y': [(initial_weight*min_x) + initial_bias,
                                             (initial_weight*max_x)+initial_bias]})
    # Add regression line to loss_figure
    with ffigg.batch_update():
      ffigg.add_trace(go.Scatter(x=regression_line_data['x'],
                                      y=regression_line_data['y'],
                                      name='Interim model',
                                      mode='lines',
                                      line_color='green',
                                      line_width=3))

  # Check to see if loss traces should be on or off
  create_loss_traces(change)



def move_regression_line(change):
  global new_weight
  global new_bias
  global points
  new_weight = weight.get_interact_value()
  new_bias = bias.get_interact_value()
  x_as_list = list(points['x'])
  y_as_list = list(points['y'])
  # Regression line data
  # Get smallesst x and y to graph first point for regression line.
  min_x = min(x_as_list)
  max_x = max(x_as_list)
  min_y = min(y_as_list)
  max_y = max(y_as_list)
  regression_line_data = pd.DataFrame({'x': [min_x, max_x],
                                       'y': [(new_weight*min_x) + new_bias,
                                             (new_weight*max_x)+ new_bias]})

  with ffigg.batch_update():
    print("Trying to move regression line")
    ffigg.update_traces(x=regression_line_data['x'],
                              y=regression_line_data['y'],
                              selector=dict(name='Interim model'))

  # Update loss line traces when regression line moves
  if (len(ffigg.data)>2):
    for i in range(len(loss_trace_points)):
      ffigg.update_traces(selector=dict(name='loss_trace_'+ str(i)),
                                x=[points['x'][i], points['x'][i]],
                                y=[points['y'][i], weight.get_interact_value()*points['x'][i]+bias.get_interact_value()])
    ######
    # Update loss widget values
    ######
    if (loss_lines_on.get_interact_value()== True):
      get_loss_trace_points(points)
      mse.value = "{:.2f}".format(get_mse())


noise.observe(add_data_points)
loss_lines_on.observe(create_loss_traces)
bias.observe(move_regression_line)
weight.observe(move_regression_line)

display(
    noise,
    bias,
    weight,
    mse, loss_lines_on, ffigg)

willgoldby avatar Apr 17 '23 18:04 willgoldby

Just checking in on this. :) Thanks!

willgoldby avatar Apr 20 '23 03:04 willgoldby

Hey, sorry, this code is really long and complicated... are you able to find a more minimal test case that reproduces the bug?

nicolaskruchten avatar Apr 20 '23 12:04 nicolaskruchten

Hey, sorry, this code is really long and complicated... are you able to find a more minimal test case that reproduces the bug?

Let me give a shorter test case to reproduce this bug.

First, below code is about using seaborn and matplotlib to plot a heatmap and it works.

import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd

correlation_matrix = pd.DataFrame(
    {
        "a": [1,2,3,4,5,6,7,8,9,10,11,12],
        "b": [2,3,4,5,3,4,5,7,8,9,14,7],
        "c": [7,7,7,5,3,14,5,7,15,9,14,7],
    }
).corr()

plt.figure(figsize=(10, 8)) 
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f")

plt.title('Correlation Heatmap') 
plt.xlabel('Variables') 
plt.ylabel('Variables') 

plt.show()

Then, let's try to use plotly to do the same thing.

import plotly.figure_factory as ff
import plotly.io as pio
import pandas as pd

correlation_matrix = pd.DataFrame(
    {
        "a": [1,2,3,4,5,6,7,8,9,10,11,12],
        "b": [2,3,4,5,3,4,5,7,8,9,14,7],
        "c": [7,7,7,5,3,14,5,7,15,9,14,7],
    }
).corr()

fig = ff.create_annotated_heatmap(correlation_matrix.values, 
                                  x=correlation_matrix.columns.values, 
                                  y=correlation_matrix.index.values, 
                                  annotation_text = correlation_matrix.values,
                                  colorscale='Viridis',
                                  showscale=True)

fig.update_layout(title='Correlation Heatmap', xaxis=dict(title='Variables'), yaxis=dict(title='Variables'))

fig.show()

Above code raises an error.

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[26], line 13
      3 import pandas as pd
      5 correlation_matrix = pd.DataFrame(
      6     {
      7         "a": [1,2,3,4,5,6,7,8,9,10,11,12],
   (...)
     10     }
     11 ).corr()
---> 13 fig = ff.create_annotated_heatmap(correlation_matrix.values, 
     14                                   x=correlation_matrix.columns.values, 
     15                                   y=correlation_matrix.index.values, 
     16                                   annotation_text = correlation_matrix.values,
     17                                   colorscale='Viridis',
     18                                   showscale=True)
     20 fig.update_layout(title='Correlation Heatmap', xaxis=dict(title='Variables'), yaxis=dict(title='Variables'))
     22 fig.show()

File ~/anaconda3/lib/python3.11/site-packages/plotly/figure_factory/_annotated_heatmap.py:102, in create_annotated_heatmap(z, x, y, annotation_text, colorscale, font_colors, showscale, reversescale, **kwargs)
    100 # Avoiding mutables in the call signature
    101 font_colors = font_colors if font_colors is not None else []
--> 102 validate_annotated_heatmap(z, x, y, annotation_text)
    104 # validate colorscale
    105 colorscale_validator = ColorscaleValidator()

File ~/anaconda3/lib/python3.11/site-packages/plotly/figure_factory/_annotated_heatmap.py:31, in validate_annotated_heatmap(z, x, y, annotation_text)
     26         if len(z[lst]) != len(annotation_text[lst]):
     27             raise exceptions.PlotlyError(
     28                 "z and text should have the " "same dimensions"
     29             )
---> 31 if x:
     32     if len(x) != len(z[0]):
     33         raise exceptions.PlotlyError(
     34             "oops, the x list that you "
     35             "provided does not match the "
     36             "width of your z matrix "
     37         )

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Reason

# Index(['a', 'b', 'c'], dtype='object')
if correlation_matrix.columns:
    print("above condition raises error")
# array(['a', 'b', 'c'], dtype=object)
if correlation_matrix.columns.values:
    print("above condition raises error")
# ['a', 'b', 'c']
if correlation_matrix.columns.to_list():
    print("above condition works fine")

leoknuth avatar May 13 '24 08:05 leoknuth

https://github.com/plotly/plotly.py/blob/956ab2cd158f0d20b322aa49e2f717a56aa6b9e2/packages/python/plotly/plotly/basewidget.py#L857

I'm getting the following error from line 857 in basewidget.py:

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()


Here, if this input_val is not a simple list, error happens.

import numpy as np
input_val = np.array([1,2,3])

if input_val:
    print("error happens in the condition")

To fix this, you can modify the condition to:

if input_val is not None:
    print("this condition works fine")

leoknuth avatar May 13 '24 09:05 leoknuth

In the code of plotly, many non-empty checks for the data are written incorrectly, using a shorthand that actually performs a check for true and false values. These check statements confuse the concept of None with truthiness.

@nicolaskruchten

leoknuth avatar May 14 '24 06:05 leoknuth