def plot_clients(path: Union[(str, os.PathLike)], df: pd.DataFrame, augmented: bool, speed_factor: float) -> None:
    'Plot the steer, throttle, brake, and speed of a client during its data collection'
    if path.endswith(os.sep):
        path = path[:(- 1)]
    dataset_name = os.path.basename(path)
    s_path = os.path.join(os.getcwd(), 'data_analysis', dataset_name, 'clients')
    os.makedirs(s_path, exist_ok=True)
    clients = glob.glob(os.path.join(path, '**/*'))
    clients = [cl for cl in clients if os.path.isdir(cl)]
    num_clients = len(clients)
    num_frames = len(df)
    num_frames_per_client = (num_frames // num_clients)

    def get_change_locs(df: pd.DataFrame, cli: int) -> Tuple[(List[int], List[float])]:
        'Get the index and directions from the df of the actions taken by the client'
        df['directions_str'] = df['directions'].astype(str)
        df['change'] = (df['directions_str'].shift(1, fill_value=df['directions_str'].head(1)) != df['directions_str'])
        index_change = list(df.loc[(df['change'] == True)].index.values)
        index_change = ([((cli - 1) * len(df))] + index_change)
        dirs = list(df['directions'][index_change].values)
        index_change = (index_change + [((cli * len(df)) - 1)])
        return (index_change, dirs)
    my_labels = {2.0: 'No Action', 3.0: 'Turn Left', 4.0: 'Turn Right', 5.0: 'Continue Straight'}
    colors = {2.0: 'gold', 3.0: 'gray', 4.0: 'cyan', 5.0: 'magenta'}
    total_action_counts = Counter({2.0: 0, 3.0: 0, 4.0: 0, 5.0: 0})
    max_speed_clients = {}
    idx_change_clients = {}
    dirs_clients = {}
    for client in tqdm(range(1, (num_clients + 1)), total=num_clients, unit='clients'):
        if augmented:
            df_client = df[((client - 1) * num_frames_per_client):(client * num_frames_per_client):3]
        else:
            df_client = df[((client - 1) * num_frames_per_client):(client * num_frames_per_client)]
            df_client['speed'] = df_client['speed'].div(speed_factor)
        actual_max_speed = df_client['speed'].max()
        max_speed_clients[client] = actual_max_speed
        (fig, ax) = plt.subplots(figsize=(48, 16))
        fig.tight_layout(rect=[0, 0.03, 1, 0.95])
        df_client.plot(y=['steer', 'throttle', 'brake', 'speed'], ax=ax)
        (idx_change, dirs) = get_change_locs(df_client, client)
        for (idx, dir) in enumerate(dirs):
            ax.axvspan(idx_change[idx], idx_change[(idx + 1)], facecolor=colors[dir], alpha=0.5, label=my_labels[dir])
        idx_change_clients[f'client_{client:02d}'] = [int(idx) for idx in idx_change]
        dirs_clients[f'client_{client:02d}'] = [float(d) for d in dirs]
        dirs_count = Counter(dirs)
        total_action_counts += dirs_count
        total_actions = ''
        for key in my_labels:
            total_actions += f' - {my_labels[key]}: {dirs_count[key]}'
        suptitle = f'Client {client} - Actual max speed: {actual_max_speed:.4f}'
        suptitle = (f'{suptitle} - Augmented' if augmented else suptitle)
        suptitle = f'{suptitle}{total_actions}'
        plt.suptitle(suptitle, fontsize=30)
        plt.xlabel('Frame idx', fontsize=22)
        plt.ylabel('Normed value', fontsize=22)
        plt.xticks(list(range(((client - 1) * num_frames_per_client), ((client * num_frames_per_client) + 1), (len(df_client) // 20))))
        (hand, labl) = ax.get_legend_handles_labels()
        handout = []
        lablout = []
        for (h, l) in zip(hand, labl):
            if (l not in lablout):
                lablout.append(l)
                handout.append(h)
        ax.legend(handout, lablout, fontsize='x-large')
        sname = os.path.join(s_path, f'{dataset_name}_Client{client:02d}')
        sname = (f'{sname}-aug' if augmented else sname)
        plt.savefig(f'{sname}.png', dpi=300)
        plt.close()
    actions_summary = {'avg_no_action': (total_action_counts[2.0] / num_clients), 'avg_turn_left': (total_action_counts[3.0] / num_clients), 'avg_turn_right': (total_action_counts[4.0] / num_clients), 'avg_continue_straight': (total_action_counts[5.0] / num_clients)}
    summary = {'num_clients': num_clients, 'num_frames_per_client': num_frames_per_client, 'hours_per_client': (num_frames_per_client / ((20 * 60) * 60)), 'total_action_counts': total_action_counts, 'actions_summary': actions_summary, 'max_speed_clients': max_speed_clients, 'idx_change_clients': idx_change_clients, 'dirs_clients': dirs_clients}
    with open(os.path.join(s_path, f'{dataset_name}-summary.json'), 'w') as f:
        json.dump(summary, f, indent=4)
Plot the steer, throttle, brake, and speed of a client during its data collection
def plot_clients(path: Union[(str, os.PathLike)], df: pd.DataFrame, augmented: bool, speed_factor: float) -> None:
    
    if path.endswith(os.sep):
        path = path[:(- 1)]
    dataset_name = os.path.basename(path)
    s_path = os.path.join(os.getcwd(), 'data_analysis', dataset_name, 'clients')
    os.makedirs(s_path, exist_ok=True)
    clients = glob.glob(os.path.join(path, '**/*'))
    clients = [cl for cl in clients if os.path.isdir(cl)]
    num_clients = len(clients)
    num_frames = len(df)
    num_frames_per_client = (num_frames // num_clients)

    def get_change_locs(df: pd.DataFrame, cli: int) -> Tuple[(List[int], List[float])]:
        'Get the index and directions from the df of the actions taken by the client'
        df['directions_str'] = df['directions'].astype(str)
        df['change'] = (df['directions_str'].shift(1, fill_value=df['directions_str'].head(1)) != df['directions_str'])
        index_change = list(df.loc[(df['change'] == True)].index.values)
        index_change = ([((cli - 1) * len(df))] + index_change)
        dirs = list(df['directions'][index_change].values)
        index_change = (index_change + [((cli * len(df)) - 1)])
        return (index_change, dirs)
    my_labels = {2.0: 'No Action', 3.0: 'Turn Left', 4.0: 'Turn Right', 5.0: 'Continue Straight'}
    colors = {2.0: 'gold', 3.0: 'gray', 4.0: 'cyan', 5.0: 'magenta'}
    total_action_counts = Counter({2.0: 0, 3.0: 0, 4.0: 0, 5.0: 0})
    max_speed_clients = {}
    idx_change_clients = {}
    dirs_clients = {}
    for client in tqdm(range(1, (num_clients + 1)), total=num_clients, unit='clients'):
        if augmented:
            df_client = df[((client - 1) * num_frames_per_client):(client * num_frames_per_client):3]
        else:
            df_client = df[((client - 1) * num_frames_per_client):(client * num_frames_per_client)]
            df_client['speed'] = df_client['speed'].div(speed_factor)
        actual_max_speed = df_client['speed'].max()
        max_speed_clients[client] = actual_max_speed
        (fig, ax) = plt.subplots(figsize=(48, 16))
        fig.tight_layout(rect=[0, 0.03, 1, 0.95])
        df_client.plot(y=['steer', 'throttle', 'brake', 'speed'], ax=ax)
        (idx_change, dirs) = get_change_locs(df_client, client)
        for (idx, dir) in enumerate(dirs):
            ax.axvspan(idx_change[idx], idx_change[(idx + 1)], facecolor=colors[dir], alpha=0.5, label=my_labels[dir])
        idx_change_clients[f'client_{client:02d}'] = [int(idx) for idx in idx_change]
        dirs_clients[f'client_{client:02d}'] = [float(d) for d in dirs]
        dirs_count = Counter(dirs)
        total_action_counts += dirs_count
        total_actions = 
        for key in my_labels:
            total_actions += f' - {my_labels[key]}: {dirs_count[key]}'
        suptitle = f'Client {client} - Actual max speed: {actual_max_speed:.4f}'
        suptitle = (f'{suptitle} - Augmented' if augmented else suptitle)
        suptitle = f'{suptitle}{total_actions}'
        plt.suptitle(suptitle, fontsize=30)
        plt.xlabel('Frame idx', fontsize=22)
        plt.ylabel('Normed value', fontsize=22)
        plt.xticks(list(range(((client - 1) * num_frames_per_client), ((client * num_frames_per_client) + 1), (len(df_client) // 20))))
        (hand, labl) = ax.get_legend_handles_labels()
        handout = []
        lablout = []
        for (h, l) in zip(hand, labl):
            if (l not in lablout):
                lablout.append(l)
                handout.append(h)
        ax.legend(handout, lablout, fontsize='x-large')
        sname = os.path.join(s_path, f'{dataset_name}_Client{client:02d}')
        sname = (f'{sname}-aug' if augmented else sname)
        plt.savefig(f'{sname}.png', dpi=300)
        plt.close()
    actions_summary = {'avg_no_action': (total_action_counts[2.0] / num_clients), 'avg_turn_left': (total_action_counts[3.0] / num_clients), 'avg_turn_right': (total_action_counts[4.0] / num_clients), 'avg_continue_straight': (total_action_counts[5.0] / num_clients)}
    summary = {'num_clients': num_clients, 'num_frames_per_client': num_frames_per_client, 'hours_per_client': (num_frames_per_client / ((20 * 60) * 60)), 'total_action_counts': total_action_counts, 'actions_summary': actions_summary, 'max_speed_clients': max_speed_clients, 'idx_change_clients': idx_change_clients, 'dirs_clients': dirs_clients}
    with open(os.path.join(s_path, f'{dataset_name}-summary.json'), 'w') as f:
        json.dump(summary, f, indent=4)