code: def evaluation_plot(
      x, traces, num_cols=3, ylabel=None, title=None, hline=False
  ):
      """Helper function to create evaluation plots from traces.
  
      Creates a figure with subplots.
      Every dataframe in ``traces`` has the same columns.
      There is one subplot for each column, plotting the values of
      that column from all the traces against ``x``.
  
      For example, there can be two traces, forecasts and actuals,
      each containing timeseries for multiple variables (nodes),
      represented as columns in ``df``. ``x`` can be the time
      index of the timeseries. This function returns a figure with
      a subplot for each variable, plotting forecasts against actuals.
  
      x : `numpy.array`
          x-axis values for the lines to plot.
      traces : `list` [`greykite.algo.reconcile.convex.reconcile_forecasts.TraceInfo`]
          y-axis values for the lines to plot, along with styling info.
          The columns for each dataframe in ``traces`` must be identical.
          The number of rows in each dataframe must match the length of ``x``.
      num_cols : `int`, default 3
          Number of columns in the plot.
          Controls the number of subplots to show in each row,
          before wrapping to the next row.
  
          Rows are filled in the order of the columns
          in the dataframes, from left to right.
      ylabel : `str` or None, default None
          y-axis label for each subplot.
          If None, no y-axis label is shown.
      title : `str` or None, default None
          Title for the entire plot.
          If None, no title is shown.
      hline : `bool`, default False
          Whether to show a horizontal line at y=0.
  
      Returns
      -------
      fig : `plotly.graph_objects.Figure`
          The plot object.
      """
      if len(traces) == 0:
          raise ValueError("There must be at least one trace to plot.")
      df_col_names = [trace.df.columns for trace in traces]
      col_names = df_col_names[0]
      if not all([cols.equals(col_names) for cols in df_col_names]):
          raise ValueError(
              "Column names must be identical in all traces."
          )
      if not all([len(x) == trace.df.shape[0] for trace in traces]):
          raise ValueError(
              "``x`` length must match ``df`` length for all traces."
          )
      if num_cols <= 0 or num_cols > len(col_names):
          raise ValueError(
              f"`num_cols` should be between 1 and {len(col_names)} "
              f"(the number of columns), found {num_cols}."
          )
  
      # The number of rows depends on the number of
      # subplots and the subplots per row.
      num_rows = math.ceil(traces[0].df.shape[1] / num_cols)
  
      fig = make_subplots(
          rows=num_rows,
          cols=num_cols,
          start_cell="top-left",
          subplot_titles=list(col_names),
      )
      # Creates a subplot for each column
      for i, node in enumerate(col_names):
          # Subplot position (1-indexed)
          # Fills rows from left to right,
          # top to bottom.
          row = i // num_cols + 1
          col = (i % num_cols) + 1
          # Adds y=0 line
          if hline:
              fig.add_trace(
                  go.Scatter(
                      x=x,
                      y=np.zeros_like(x),
                      name="zero",
                      line=dict(color="black", width=1),
                      legendgroup="zero",
                      showlegend=False,
                  ),
                  row=row,
                  col=col,
              )
          # Adds each trace's column
          for i, trace in enumerate(traces):
              name = (
                  f"{node}-{trace.name}"
                  if trace.name is not None
                  else node
              )
              opacity = (
                  1 if i == 0 else 0.8
              )  # adds opacity to traces after the first one
              fig.add_trace(
                  go.Scatter(
                      x=x,
                      y=np.array(trace.df[node]),
                      name=name,
                      legendgroup=trace.legendgroup,
                      line=dict(color=trace.color),
                      opacity=opacity,
                  ),
                  row=row,
                  col=col,
              )
          # Adds y-axis titles
          ytitle = go.layout.yaxis.Title(font={"size": 10}, text=ylabel)
          fig.update_yaxes(row=row, col=col, title=ytitle)
      # Adds title for the entire plot
      fig.update_layout(
          dict(title_text=title, title_x=0.5, title_font_size=20)
      )
      return fig
  
imports: import functools
  import logging
  import math
  import sys
  import warnings
  from dataclasses import dataclass
  from typing import Optional
  import cvxpy
  import numpy
  import pandas
  import plotly
  import plotly.express
  from plotly import graph_objects
  from plotly.subplots import make_subplots
  from scipy.linalg import sqrtm
  from sklearn.exceptions import NotFittedError
  from greykite.algo.reconcile.hierarchical_relationship import HierarchicalRelationship
  from greykite.common.python_utils import reorder_columns
label_desc: Use image node_id 4 for calling a global function with example usage: evaluation_plot(x, traces, num_cols, ylabel, title, hline) and returns: fig