Если у меня есть такой код:
import matplotlib.pyplot as plt
import numpy as np
time_axis = np.arange(0, 30 + 1 / 50, 1 / 50)
another_axis = np.arange(0, 10, 1)
grid = np.zeros((len(another_axis), len(time_axis)))
fig, ax = plt.subplots(12, 1, figsize=(10, 15), constrained_layout=True)
for i in range(12):
grid = np.random.random((len(another_axis), len(time_axis)))
pcm = ax[i].pcolormesh(time_axis, another_axis, grid, vmin=0, vmax=1)
fig.colorbar(pcm, ax=ax[i])
plt.show()
У меня проблема, когда не все 12 сеток сразу помещаются в память. В момент вызова plt.show()
удерживает ли matplotlib память, пропорциональную 12*grid.size
? Могу ли я что-нибудь сделать, чтобы уменьшить это, или это просто способ работы графиков и pcolormesh?
Вам следует обновить фрагмент кода до длины данных, соответствующей вашей проблеме с памятью, а также, возможно, указать объем оперативной памяти, доступной на вашем компьютере. Насколько я могу судить, рисунки matplotlib, показанные с помощью plt.show()
, содержат все данные, которые вы даете mpl для рисования в каждом ax
, такой вещи, как черная магия, не существует. Если у вас действительно мало памяти, рассмотрите возможность сохранения каждого графика в файл и избавьтесь от части «plt.show()».
см. также dev.to/siddhantkcode/… stackoverflow.com/questions/7101404/… stackoverflow.com/questions/2364945/… ?
В момент вызова plt.show() удерживает ли matplotlib память, пропорциональную 12*grid.size?
На основе небольшого теста ниже: Да
Могу ли я что-нибудь сделать, чтобы уменьшить это, или это просто способ работы графиков и pcolormesh?
Нет, уменьшить нельзя. Именно так работает построение графиков. Ниже приведены некоторые оптимизации времени рендеринга, которые в основном связаны с уменьшением количества линий или точек, то есть «расхламлением». Обратите внимание, что некоторые веб-решения используют Дугласа-Пакера для упрощения линий в зависимости от масштаба.
например листовка https://leafletjs.com/reference.html#lineutil-simplify
Значительно сокращает количество точек в полилинии, сохраняя при этом ее форму, и возвращает новый массив упрощенных точек с использованием алгоритма Рамера-Дугласа-Пейкера. Используется для значительного повышения производительности при обработке/отображении полилиний Leaflet для каждого уровня масштабирования, а также для уменьшения визуального шума. допуск влияет на степень упрощения (меньшее значение означает более высокое качество, но медленнее и с большим количеством баллов). Также выпущен как отдельная микробиблиотека Simplify.js.
например фолиум Есть ли ограничения на рисование маркерами с фолиумом?
Что касается сообщения о награде:
Было бы неплохо, если бы сюжет можно было частично визуализировать, чтобы сэкономить место или что-то в этом роде.
В зависимости от данных (я предполагаю, что это не какой-то случайный шум) вы можете рассмотреть возможность использования контура вместо цветовой сетки, как показано здесь: https://matplotlib.org/stable/gallery/images_contours_and_fields/pcolormesh_levels.html#pcolormesh
Я не проверял производительность обоих, но тест на примере matplotlib вполне выполним, просто добавьте больше очков.
В противном случае вы можете просто сохранить отдельные сюжеты в виде фигур в файловой системе, но вы, очевидно, потеряете интерактивность.
Другой подход заключается в выполнении некоторой аналитики, такой как визуализация распределения (kde,Whickerbox,...), регрессия, попытка соответствовать законам CDF/PDF, кластеризация... все это может программно помочь вам сузить список до релевантных. данные. См. предложенную стратегию в этом ответе (см. «Инструменты и контрольные показатели» ниже).
Я провел небольшой тест, используя программу оценки размера объекта Python, основанную на этой теме SO. Однако казалось совершенно очевидным, что структура matplotlib будет линейно расти с добавлением данных.
Размер риса в памяти увеличивается линейно с nb временных шагов ваших данных и nb подграфиков.
import itertools
import sys
from collections import deque
from collections.abc import Mapping, Set
from gc import get_referents
from numbers import Number
from pathlib import Path
from types import FunctionType, ModuleType
import numpy as np
import pandas as pd
import seaborn as sns
print(sys.version)
import tqdm as __tqdm
import matplotlib as mpl
for _m in [np, pd, __tqdm, mpl, sns, ]:
print(f"{_m.__name__:>14s} - {_m.__version__:12s}")
import matplotlib.pyplot as plt
from tqdm.auto import tqdm
# Custom objects know their class.
# Function objects seem to know way too much, including modules.
# Exclude modules as well.
BLACKLIST = type, ModuleType, FunctionType
def getsize_v1(obj):
"""
sum size of object & members.
https://stackoverflow.com/a/30316760/7237062
first function
"""
if isinstance(obj, BLACKLIST):
raise TypeError('getsize() does not take argument of type: ' + str(type(obj)))
seen_ids = set()
size = 0
objects = [obj]
while objects:
need_referents = []
for obj in objects:
if not isinstance(obj, BLACKLIST) and id(obj) not in seen_ids:
seen_ids.add(id(obj))
size += sys.getsizeof(obj)
need_referents.append(obj)
objects = get_referents(*need_referents)
return size
ZERO_DEPTH_BASES = (str, bytes, Number, range, bytearray)
def getsize_custom(obj_0):
"""
Recursively iterate to sum size of object & members.
https://stackoverflow.com/a/30316760/7237062
2nd implementation
"""
_seen_ids = set()
def inner(obj):
obj_id = id(obj)
if obj_id in _seen_ids:
return 0
_seen_ids.add(obj_id)
size = sys.getsizeof(obj)
if isinstance(obj, ZERO_DEPTH_BASES):
pass # bypass remaining control flow and return
elif isinstance(obj, (tuple, list, Set, deque)):
size += sum(inner(i) for i in obj)
elif isinstance(obj, Mapping) or hasattr(obj, 'items'):
size += sum(inner(k) + inner(v) for k, v in getattr(obj, 'items')())
# Check for custom object instances - may subclass above too
if hasattr(obj, '__dict__'):
size += inner(vars(obj))
if hasattr(obj, '__slots__'): # can have __slots__ with __dict__
size += sum(inner(getattr(obj, s)) for s in obj.__slots__ if hasattr(obj, s))
return size
return inner(obj_0)
# don't let matplotlib try things related to rendering (ex: if using an interactive console)
mpl.rcParams["interactive"] = False
# takes time, so save data and analyze later
root_dir = Path.home() / "StackOverflow" / "Q_78714345"
root_dir.mkdir(parents=True, exist_ok=True)
output_file_path_fig = (root_dir / "results_fig.csv").expanduser().absolute()
output_file_path_subplots = (root_dir / "results_subplots.csv").expanduser().absolute()
print(output_file_path_fig.as_uri()) # I have very bad memory
print(output_file_path_subplots.as_uri()) # I have very bad memory
# benchmark fig and subplot memory inflation
print("BENCHMARK".center(50, " ").center(80, "*", ))
subplots_results = []
fig_results = []
# nb_subplots = 12
subplots = [2, 4, 8, 16, 32]
data_points = [10, 50, 100, 500, 1_000, 5_000, 10_000, 25_000] # can't go futher, low perf computer at hand ...
combinatorics = list(itertools.product(subplots, data_points))
another_axis = np.arange(0, 10, 1)
for nb_subplots, time_steps in tqdm(combinatorics, desc = "Generating test cases"):
time_axis = np.arange(0, 30 + 1 / time_steps, 1 / time_steps)
grid = np.zeros((len(another_axis), len(time_axis)))
# print(f"Grid shape {grid.shape}".center(50, " ").center(80, "*", ))
fig, ax = plt.subplots(nrows=nb_subplots, ncols=1, ) # figsize=(10, 15), constrained_layout=True)
initial_fig_size = getsize_custom(fig)
for i in range(nb_subplots):
_ax = ax[i]
initial = getsize_custom(_ax)
grid = np.random.random((len(another_axis), len(time_axis)))
pcm = _ax.pcolormesh(time_axis, another_axis, grid, vmin=0, vmax=1)
fig.colorbar(pcm, ax=_ax)
final = getsize_custom(_ax)
# subplots_results.append((time_steps, nb_subplots, i, initial, "initial"))
# subplots_results.append((time_steps, nb_subplots, i, final, "final"))
# subplots_results.append((time_steps, nb_subplots, i, final / initial, "ratio"))
final_fig_size = getsize_custom(fig)
fig_results.append((time_steps, nb_subplots, initial_fig_size, "initial"))
fig_results.append((time_steps, nb_subplots, final_fig_size, "final"))
fig_results.append((time_steps, nb_subplots, final_fig_size / initial_fig_size, "ratio"))
# plt.show() # don't !
plt.clf()
plt.close()
del fig
del ax
# df_subplots = pd.DataFrame(
# subplots_results,
# columns=["Time steps", "nb_subplots", "subplot nb", "size", "Moment"]
# )
df_figure = pd.DataFrame(
fig_results,
columns=["Time steps", "nb_subplots", "Fig size", "Moment"]
)
print("SAVE DATAFRAMES".center(50, " ").center(80, "*", ))
df_figure.to_csv(output_file_path_fig, sep = ";", index=False)
# df_subplots.to_csv(output_file_path_subplots, sep = ";", index=False)
tmp_ = df_fig[df_fig["Moment"] != "ratio"].copy()
tmp_["Moment"] =tmp_["Moment"].astype("category")
tmp_["nb_subplots"] =tmp_["nb_subplots"].astype("category")
sns.set_theme(style = "darkgrid")
sns.set_context("paper")
g = sns.relplot(data=tmp_, x = "Time steps", y = "Fig size", style = "Moment", hue = "nb_subplots", kind = "line", markers=True,)
g.set_ylabels("Size in bytes")
plt.show()
используемые версии:
3.9.6 (tags/v3.9.6:db3ff76, Jun 28 2021, 15:26:21) [MSC v.1929 64 bit (AMD64)]
numpy - 2.0.0
pandas - 2.2.2
tqdm - 4.66.4
matplotlib - 3.9.1
seaborn - 0.13.2
Возможно, вы захотите прочитать [этот вопрос/ответ] (Большой интерактивный график с ~20 миллионами точек выборки и гигабайтами данных)
matplotlib не смог обработать 10 миллионов точек. Другие инструменты сделали это. см. «Сводка результатов»
В зависимости от точных деталей вашей проблемы вам следует подумать о чем-то другом. В посте выше содержится обзор нескольких инструментов для работы с большими данными.
Но я не увидел ничего, связанного с цветовой сеткой, хотя подозреваю, что этот конкретный график приведет к тем же результатам.
Цитирую https://matplotlib.org/stable/users/explain/artists/ Performance.html
Независимо от того, исследуете ли вы данные в интерактивном режиме или программно сохраняете множество графиков, производительность рендеринга может стать узким местом в вашем конвейере. Matplotlib предоставляет несколько способов значительно сократить время рендеринга за счет небольшого изменения (до заданного допуска) внешнего вида вашего графика. Доступные методы сокращения времени рендеринга зависят от типа создаваемого графика.
Они указывают на то, что вы можете настроить производительность рендеринга дисплея с помощью некоторых настроек, приведенных ниже (вкратце воспроизведено здесь, ссылка подробно описывает, как и почему).
Однако здесь нет абзаца, связанного со стратегией построения элементов в памяти. Если только вы сами не сократите набор предметов для отображения, чего вам, похоже, не хочется.
mpl.rcParams['path.simplify'] = True
mpl.rcParams['path.simplify_threshold'] = 0.0
# or
mpl.rcParams['path.simplify_threshold'] = 1.0
plt.plot(x, y, markevery=10)
import matplotlib.style as mplstyle
mplstyle.use('fast')
Кроме того, в по этой ссылке автор подробно рассказывает о некоторых ситуациях с утечками памяти в matplotlib и о том, как с ними бороться.
Это сводится к:
# plot figures
# ...
# properly clean and close
plt.clf()
plt.close()
Но опять же, это работает, только если matplotlib не взрывается на шаге plt.show().
Что касается внешнего интерфейса matplotlib, вы мало что можете сделать, чтобы получить интерактивный график с большим количеством данных.
Что касается серверной части, у вас могут быть другие варианты, но я не могу сказать, какой из них работает.
См. эту страницу документации Matplotlib по бэкэндам
Есть три способа настроить серверную часть:
- Параметр rcParams["backend"] в вашем файле matplotlibrc.
- Переменная среды MPLBACKEND
- Функция matplotlib.use()
Затем все идет на большую глубину:
Чтобы упростить настройку графических пользовательских интерфейсов, Matplotlib отделяет концепцию средства рендеринга (того, что фактически выполняет рисование) от холста (места, где происходит рисование). Каноническим средством визуализации пользовательских интерфейсов является Agg, который использует библиотеку C++ Anti-Grain Geometry для создания растрового (пиксельного) изображения фигуры; он используется серверными модулями QtAgg, GTK4Agg, GTK3Agg, wxAgg, TkAgg и macosx. Альтернативный рендерер основан на библиотеке Cairo, используемой QtCairo и т. д.
Я предполагаю, что многие оптимизации рендеринга происходят внутри Agg. Если там произойдет взрыв ОЗУ (то есть не раньше, чем во внешней части...), вы мало что сможете сделать. Могу поспорить, что код C++ достаточно оптимизирован.
Что касается механизмов рендеринга, пользователи также могут различать векторные и растровые средства рендеринга. Языки векторной графики выдают команды рисования типа «нарисовать линию от этой точки до этой точки» и, следовательно, не масштабируются. Растровые серверы генерируют пиксельное представление линии, точность которого зависит от настройки DPI.
Там вы можете найти список альтернативных интерактивных бэкэндов. Может, попробовать векторные?
@cottontail Привет, да, это действительно вызывает проблемы, если учесть, что сетка намного больше. Я хочу понять, как меняется память после завершения цикла и добавления pcolormesh к сюжету. Это связано с тем, что в моем случае не все 12 сеток могут (одновременно, если не считать сокращения) поместиться в памяти.