code: def check_preprocessed_df_equal(df1, df2):
      for column in df1.columns:
          vals1 = df1[column].values
          vals2 = df2[column].values
  
          if any(
              feature_name in column
              for feature_name in [BINARY, CATEGORY]
          ):
              is_equal = np.all(vals1 == vals2)
          elif any(feature_name in column for feature_name in [NUMBER]):
              is_equal = np.allclose(vals1, vals2)
          elif any(
              feature_name in column
              for feature_name in [
                  SET,
                  BAG,
                  H3,
                  DATE,
                  TEXT,
                  SEQUENCE,
                  TIMESERIES,
                  VECTOR,
              ]
          ):
              is_equal = np.all(
                  [np.all(rv == lv) for rv, lv in zip(vals1, vals2)]
              )
          elif any(
              feature_name in column for feature_name in [AUDIO, IMAGE]
          ):
              is_equal = True
              for v1, v2 in zip(vals1, vals2):
                  # We reshape both because there is a difference after preprocessing across the two backends.
                  # With the distributed backend, the data is flattened and then later reshaped to its original shape
                  # during training. With the local backend, the data is kept its original shape throughout.
                  # TODO: Determine whether this is desired behavior. Tracked here:
                  # https://github.com/ludwig-ai/ludwig/issues/2645
                  v1 = v1.reshape(-1)
                  v2 = v2.reshape(-1)
                  is_equal &= np.allclose(v1, v2, atol=1e-5)
                  if not is_equal:
                      break
          assert (
              is_equal
          ), f"Column {column} is not equal. Expected {vals1[:2]}, got {vals2[:2]}"
  
imports: import copy
  import os
  import tempfile
  from typing import Literal, Optional, Union
  import numpy
  import pandas
  import pytest
  import torch
  from ludwig.api import LudwigModel
  from ludwig.backend import create_ray_backend, initialize_backend, LOCAL_BACKEND
  from ludwig.constants import AUDIO, BAG, BALANCE_PERCENTAGE_TOLERANCE, BATCH_SIZE, BFILL, BINARY, CATEGORY, COLUMN, DATE, H3, IMAGE, MAX_BATCH_SIZE_DATASET_FRACTION, NAME, NUMBER, PREPROCESSING, SEQUENCE, SET, SPLIT, TEXT, TIMESERIES, TRAINER, VECTOR
  from ludwig.data.preprocessing import balance_data
  from ludwig.data.split import DEFAULT_PROBABILITIES
  from ludwig.utils.data_utils import read_parquet
  from ludwig.utils.misc_utils import merge_dict
  from tests.integration_tests.utils import audio_feature, augment_dataset_with_none, bag_feature, binary_feature, category_feature, create_data_set_to_use, date_feature, generate_data, h3_feature, image_feature, number_feature, RAY_BACKEND_CONFIG, sequence_feature, set_feature, text_feature, timeseries_feature, train_with_backend, vector_feature
  import dask
  import ray
  import ray.exceptions
  from ray.air.config import DatasetConfig
  from ray.data import Dataset, DatasetPipeline
  from ray.train._internal.dataset_spec import DataParallelIngestSpec
  from ludwig.backend.ray import get_trainer_kwargs, RayBackend
  from ludwig.data.dataframe.dask import DaskEngine
label_desc: Use image node_id 6 for calling a global function with example usage: check_preprocessed_df_equal(df1, df2) without return types