Animated dendrogram

I want a dendrogram to be animated and i want the branches to appear when they are clustered together. So lines between the the smallest distance would appear first and so on… I’ve tried so many things but i can not figure it out any help would be greatly appreciated.

import plotly.figure_factory as ff
import plotly.express as px

# Load the data from the CSV file
data = pd.read_csv('/Users/kelse/seniorcomp/tswift.csv')

# Extract the audio features from the data
audio_features = data[["energy", "acousticness", "mode","instrumentalness", "loudness","tempo","danceability",'speechiness','valence' , "liveness", "time_signature","key"]]
song_titles = data["title"]

# Calculate the distances between the songs
distances = linkage(audio_features, method='ward')
def ensure_consistent_dimensions(distances, song_titles):
    # Check the dimensions of the distances and song_titles variables
    print("distances shape:", distances.shape)
    print("song_titles shape:", song_titles.shape)
    
    # Check if the dimensions are consistent
    if distances.shape[0] != song_titles.shape[0]:
        # If the dimensions are not consistent, find the minimum number of elements
        min_elements = min(distances.shape[0], song_titles.shape[0])
        
        # Trim the distances and song_titles variables to the minimum number of elements
        distances = distances[:min_elements]
        song_titles = song_titles[:min_elements]
        
        print("Trimmed distances shape:", distances.shape)
        print("Trimmed song_titles shape:", song_titles.shape)
    
    # Return the distances and song_titles variables with consistent dimensions
    return distances, song_titles
# Ensure that the distances and song_titles variables have consistent dimensions
distances, song_titles = ensure_consistent_dimensions(distances, song_titles)

# Determine the number of clusters
num_clusters = determine_num_clusters(audio_features)

# Cluster the songs based on their audio features
model = AgglomerativeClustering(n_clusters=num_clusters)
model.fit(audio_features)

# Predict the cluster labels for each song
cluster_labels = model.labels_

# Create a dendrogram plot
fig = ff.create_dendrogram(distances, labels=song_titles.tolist(), color_threshold=num_clusters)

fig.update_layout(width=2000, height=2000)


# Add the animation parameter to the figure
fig.update_layout(updatemenus=[
    dict(
        buttons=list([
            dict(
                args=[{'yaxis.range': [0, 1]}, {'frame': {'duration': 500, 'redraw': True}, 'fromcurrent': True, 'transition': {'duration': 300, 'easing': 'quadratic-in-out'}}],
                label='Play',
                method='animate'
            )
        ]),
        direction='left',
        pad={'r': 10, 't': 87},
        showactive=True,
        type='buttons',
        x=0.1,
        xanchor='right',
        y=0,
        yanchor='top'
    ),
])

# Color the clusters with different colors
fig.data[0].marker.color = px.colors.qualitative.Plotly[:num_clusters]

# Initialize the Dash app
app = dash.Dash()

# Add the dendrogram plot to the Dash app
app.layout = html.Div([
    dcc.Graph(figure=fig)
])
app.run_server(host='localhost', port=8057)

Ive also tried this but this has an error of “NONETYPE object not iterable”

  import plotly.figure_factory as ff
  
  # Load the data from the CSV file
  data = pd.read_csv('/Users/kelse/seniorcomp/tswift.csv')
  
  # Extract the audio features from the data
  audio_features = data[["energy", "acousticness", "mode","instrumentalness", "loudness","tempo","danceability",'speechiness','valence' , "liveness", "time_signature","key"]]
  song_titles = data["title"]
  # Check if the song_titles variable is None
  if song_titles is None:
      # Set the song_titles variable to an empty list
      song_titles = []
  
  
  # Calculate the distances between the songs
  distances = linkage(audio_features, method='ward')
  
  def ensure_consistent_dimensions(distances, song_titles):
      # Check the dimensions of the distances and song_titles variables
      print("distances shape:", distances.shape)
      print("song_titles shape:", song_titles.shape)
      
      # Check if the dimensions are consistent
      if distances.shape[0] != song_titles.shape[0]:
          # If the dimensions are not consistent, find the minimum number of elements
          min_elements = min(distances.shape[0], song_titles.shape[0])
          
          # Trim the distances and song_titles variables to the minimum number of elements
          distances = distances[:min_elements]
          song_titles = song_titles[:min_elements]
          
          print("Trimmed distances shape:", distances.shape)
          print("Trimmed song_titles shape:", song_titles.shape)
      
      # Return the distances and song_titles variables with consistent dimensions
      return distances, song_titles
  
  # Ensure that the distances and song_titles variables have consistent dimensions
  distances, song_titles = ensure_consistent_dimensions(distances, song_titles)
  
  # Sort the distances matrix
  distances[:,2].sort()
  
  # Create a dendrogram plot
  fig = ff.create_dendrogram(distances, labels=song_titles.tolist())
  # Create a list of frames
  frames = []
  for i in range(len(distances)):
      frame = {'data': [], 'name': str(i)}
      dendro_leaves = fig['layout']['yaxis']['ticktext']
      dendro_distances = list(fig['layout']['yaxis']['tickvals'])
      for j in range(len(dendro_leaves)):
          inds = [k for k, x in enumerate(song_titles) if x == dendro_leaves[j]]
          frame['data'].append({'x': list(audio_features.iloc[inds, :].iloc[0]), 
                                'y': dendro_distances[j], 
                                'text': dendro_leaves[j], 
                                'mode': 'markers', 
                                'marker': {'size': 12}})
      frames.append(frame)
      fig.frames = frames
      # Check if the fig object has a yaxis attribute in its layout
      if 'yaxis' in fig['layout']:
          # Set the dendro_leaves and dendro_distances variables
          dendro_leaves = fig['layout']['yaxis']['ticktext']
          dendro_distances = list(fig['layout']['yaxis']['tickvals'])
      else:
          # Set the dendro_leaves and dendro_distances variables to empty lists
          dendro_leaves = []
          dendro_distances = []
      
      # Iterate over the leaves in the dendrogram
      for j in range(len(dendro_leaves)):
          # Find the indices of the songs with the current title
          inds = [k for k, x in enumerate(song_titles) if x == dendro_leaves[j]]
          # Append a trace to the frame data
          frame['data'].append({'x': list(audio_features.iloc[inds, :].iloc[0]), 
                                'y': dendro_distances[j], 
                                'text': dendro_leaves[j], 
                                'mode': 'markers', 
                                'marker': {'opacity': 0.5, 'size': 14}})
      # Append the frame to the list of frames
      frames.append(frame)
  
  # Update the layout with the frames
  fig.update_layout(updatemenus=[
      dict(
          buttons=list([
              dict(
                  args=[{'yaxis.range': [0, 1]}, {'frame': {'duration': 500, 'redraw': True}, 'fromcurrent': True, 'transition': {'duration': 300, 'easing': 'quadratic-in-out'}}],
                  label='Play',
                  method='animate'
              )
          ]),
          direction='left',
          pad={'r': 10, 't': 87},
          showactive=True,
          type='buttons',
          x=0.1,
          xanchor='right',
          y=0,
          yanchor='top'
      ),
  ], frames=frames)
  
  # Initialize the Dash app
  app = dash.Dash()
  
  # Add the dendrogram plot to the Dash app
  app.layout = html.Div([
      dcc.Graph(figure=fig)
  ])
  
  if __name__ == '__main__':
      app.run_server(host='0.0.0.0', port=8051)

HI @kpettrone, welcome to the forums.

Could you provide your data tswift.csv (or an extract of it)?