Как отображать динамические (масштабируемые) метки времени на оси Y тепловой карты в Python

Я читаю файл csv, содержащий 1 столбец с датами, 1 столбец со временем и 120 столбцов целых чисел. Файл может содержать много строк (их может быть более 100 000).

Точки данных преобразуются в индексы цвета от 0 до 1, и я отображаю их на тепловой карте.

Моя проблема заключается в отображении столбца времени в виде меток оси Y.

Что бы я ни пробовал, метки отображаются в нечитаемом виде. Полный просмотр - этикетки неперекрашиваются

Если я увеличу масштаб, чтобы увидеть больше деталей цвета, метки станут более читабельными. Увеличьте - этикетки читаются

Я хотел бы, чтобы метки были читабельны в любом виде, даже когда они полностью видны. Допустимо, что некоторые метки не будут отображаться при уменьшении масштаба и будут отображаться только при увеличении.

Я попытался создать y-тики, как показано ниже, где «данные» — это фрейм данных pandas, а case_times — это фрейм данных с объектами datetime.

import numpy as np
import pandas as pd
from datetime import datetime

data = pd.read_csv(csvfile, delimiter=r',|\s|\|', header=None)
data = np.split(dsa_data, [1, 2, 62, 122], axis=1)
case_times = dsa_data[1]
case_times[1] = pd.to_datetime(case_times[1], format='%H:%M:%S').dt.time
case_times = np.squeeze(case_times)

fig, axs = plt.subplots(1,2)
fig.tight_layout()
im1 = axs[0].imshow(data, cmap='DSAColorMap')
    
    
axs[0].set_yticks(ticks=np.arange(1, len(case_times)+1, 1), labels=case_times)
axs[0].set_autoscale_on(True)
plt.show()

Затем я попытался начать со столбца строк вместо кадра данных, но получил те же результаты:

with open(fileName, newline='') as csvfile:
    # Advance rows 3 time to skip headers and extract case start date 
    # without the need to iterate over the file
    row = next(csvfile)
    row = next(csvfile)
    row = next(csvfile) 
    text_to_split = re.split(r',|\s|\|', row)
    case_start_date = text_to_split[0]
    # Remove blank items created after split by '|' delimiter
    text_to_split = text_to_split[:len(text_to_split)-3]
    #Create a list of time stamps to be later used as x axis for the plot
    case_times.append(text_to_split.pop(1)) 
    # Create the first data row
    data.append(text_to_split[1:61])
    spamreader = csv.reader(csvfile, delimiter='|')
    # Iterate over all remaining rows
    # to create the data rows
    for row in spamreader:
            text_to_split = ' '.join(row) # Create a list from all elements of data in a row
            text_to_split = re.split(r',|\s', text_to_split) # split row by delimiters
            text_to_split.pop(len(text_to_split)-1) # Remove last element of the row which is blank
            text_to_split.pop(0) # Remove date from row
            #Create a list of time stamps to be later used as axis labels for the plot
            case_times.append(text_to_split.pop(0)) 
            # Add data row to each hemisphere data list
            data.append(text_to_split[:60])

converted_case_times = [] # transform from string to datetime
for i in range(len(case_times)):
    converted_case_times.append(datetime.strptime(case_times[i], '%H:%M:%S'))
    
fig, axs = plt.subplots(1,2)
fig.tight_layout()
im1 = axs[0].imshow(dsa_data_left, cmap='DSAColorMap')
axs[0].set_yticks(ticks=range(1, len(converted_case_times)+1), labels=converted_case_times)
plt.show()

Я также попробовал следующее изменение форматирования, чтобы решить проблему, но это тоже не сработало:

from matplotlib.dates import DateFormatter

axs[0].yaxis.set_major_formatter(DateFormatter("%H:%M:%S"))
    plt.setp(axs[0].get_yticklabels(), rotation=45, ha = "right",
         rotation_mode = "anchor")

Я предполагаю, что проблема заключается в создании тиков (как фиксированного диапазона), однако я не знаю, как решить эту проблему, и не смог найти объяснения или примера, который работал бы в моем случае.

Обновлено: После полученного ответа я добавил это в свой код:

    def generate_yticks(ax, case_times, ticks):
        ylims = ax.get_ylim()
        ticks_in_window = ticks[ticks>ylims[1]][ticks<ylims[0]]
        shown_ticks = 
        ticks_in_window[np.arange(10)*int(len(ticks_in_window)/10)]
        shown_labels = case_times[shown_ticks-1]
        ax.set_yticks(ticks=shown_ticks, labels=shown_labels)
        print(ylims)

   ticks = np.arange(1, len(case_times)+1)
   axs[0].callbacks.connect('ylim_changed', generate_yticks(axs[0], 
         case_times, ticks))

Похоже, что функция вызывается только один раз, то есть она не вызывается снова при увеличении масштаба. Я попытался изменить «ylim_changed» на другое событие, но получил сообщение об ошибке «неподдерживаемое событие».

Если проблема заключается в методе использованияgener_ticks, т.е. у него более одного параметра, я не знаю, как передать тики и case_times в функцию, поскольку это функция, вызываемая другой функцией. Я предпочитаю не делать галочки глобальной переменной, и я определенно не могу сделать переменную case_times глобальной, поскольку она генерируется внутри кода.

Я попытался вложить функциюgenerate_ticks в вызывающую функцию, но это снова вызвало ошибку «логический индекс не соответствует индексированному массиву вдоль оси 0; размер оси равен 268, но размер соответствующей логической оси равен 503».

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
0
65
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Самый простой способ добиться этого — просто удалить часть ярлыков.

Изменение предпоследней строки в первом блоке кода:

indices = np.arange(0,len(case_times), 100)
axs[0].set_yticks(ticks=indices+1, labels=case_times[indices])

Здесь я установил, чтобы он принимал только один элемент каждые 100, используя необязательный параметр step для arange. Возможно, вам придется увеличить этот шаг в зависимости от количества имеющихся у вас ярлыков; идея может состоять в том, чтобы просто использовать

indices = np.arange(n)/n * len(case_times)

который будет просто отображать метки n на одинаковом расстоянии друг от друга.

Обновлено: разъяснение после комментария

Я считаю, что вам также следует взглянуть на ax[0].callbacks.connect и создать функцию, которая устанавливает индексы по мере необходимости. Это должно выглядеть примерно так:

ticks = np.arange(1, len(case_times)+1)
def generate_yticks(ax):
    ylims = ax.get_ylim()
    ticks_in_window = ticks[np.logical_and(ticks>ylims[0], ticks<ylims[1])]
    shown_ticks = ticks_in_window[np.arange(n)/n * len(ticks_in_window)]
    shown_labels = case_times[shown_ticks-1]
    ax.set_yticks(ticks=shown_ticks, labels=shown_labels)

ax[0].callbacks.connect("ylim_changed", generate_yticks)

Спасибо, это сработало для полного просмотра. Однако, когда я увеличил масштаб, я потерял все метки, потому что теперь они были слишком разнесены, а увеличенное изображение было меньше, чем расстояние. Я не смогу контролировать, насколько пользователь приложения будет увеличивать масштаб или сколько строк будет иметь файл. Есть ли способ сделать это динамичным, чтобы оно выбирало подходящее расстояние для полного просмотра и менялось при масштабировании? Если я просто использую цифры тепловой карты, это, кажется, работает. В тот момент, когда я меняю метки на «пользовательские», метки больше не масштабируются автоматически.

syseng 17.07.2024 06:47

Я вижу, я частично неправильно понял ваш вопрос. Я обновлю свой ответ, включив в него это

dbac 17.07.2024 10:22

Спасибо за редактирование. Я не понимаю эту строку: 'ticks_in_window = Tickets[ticks>ylims[0]][ticks<ylims[1]]' Я продолжаю получать сообщение об ошибке: «логический индекс не соответствует индексированному массиву по оси 0; размер оси равен 1, но размер соответствующей логической оси равен 503 дюйма (503 — это количество строк в моем фрейме данных, на котором я запустил тестовый код)

syseng 17.07.2024 17:34

Разобрался с проблемой «ticks_in_window», просто пришлось переключаться между ylims[0] и ylims[1]. Я добавил в функцию отпечаток «ylims» и вижу, что при увеличении масштаба функция больше не вызывается. Я отредактировал вопрос, включив в него новые дополнения. Был бы признателен за дополнительную помощь в решении этой проблемы.

syseng 18.07.2024 07:26

Забыл мои основы numpy: если вы попытаетесь извлечь два условия, как я, второе выдаст ошибку, поскольку длина условия больше не соответствует длине массива, к которому вы его применяете. Сразу же изменю это в ответе, чтобы не оставить неправильную ссылку.

dbac 18.07.2024 18:34

Что касается «не вызывать снова», это связано с тем, что вместо передачи объекта функции обратному вызову вы передали результат вызова функции (который равен None). Я не думаю, что можно передавать параметры функции, используемой обратным вызовом; однако, пока вы определяете функцию внутри своей локальной области, она должна иметь доступ к переменным.

dbac 18.07.2024 18:42

Спасибо, я понял, как вызывать функцию каждый раз, встраивая ее в вызывающую функцию и выполняя расчет других «аргументов» (которые больше не являются аргументами) вне функции, но внутри вызывающей функции. Мне все еще нужно найти способ динамической установки "n" для правильного изменения в соответствии с пределами сюжета, но это не вопрос программирования :-)

syseng 18.07.2024 21:29

Другие вопросы по теме