Python matplolib,每天每 2 小时绘制一次值

问题描述 投票:0回答:1

我在使用 matplotlib 在 python 中以某种方式绘图时遇到了麻烦。 我正在制作一个接口,从芯片接收数据,然后将数据以 csv 格式保存,每天一个 csv(简历名称的格式为 YYYY-MM-DD),列是时间戳(HH -MM-SS)、DO、pH、温度,最后三个是浮点数。

目前,我有一个 4H 图表选项卡,其中绘制了三个图表(每个类别一个),另一个选项卡用于显示每个图表每天的平均值。我想更改最后一张图表,而不是平均每天一个 csv 的值,我想每天每 2 小时显示一个值,对应于每天/csv 12 个值,除非我真的不知道如何为此,我进行了很多搜索,但找不到类似的东西,有人知道该怎么做吗?另外,准确地说,我不想对这些值进行平均,它必须是每 2 小时保存一次的值。

例如,这是我目前拥有的: enter image description here (y轴是DO,x轴是日期)

我想显示的内容: enter image description here

这是我的代码,我截断了 Graph4H 类部分,因为它没有用,它更多地与 Graph24H 类有关:

from settings import *
from matplotlib.figure import Figure
from matplotlib.widgets import RectangleSelector
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from settings_menu import Widget

class Graph(ctk.CTkFrame):
    def __init__(self, master: Misc, type: Literal["4H", "24H"], title: str, ylabel: str, color: str, graphs: list[FigureCanvasTkAgg], data: dict[str, list[float]]):
        super().__init__(master=master, corner_radius=0)
        self.app = master.master.master
        self._type = type # Type de graphique 4H ou 24H
        self.title = title # Titre du graphique
        self.ylabel = ylabel # Nom de l'axe des ordonnées
        self.data = data
        self.color = color

        # Extraction de la catégorie du graphique à partir du titre pour pouvoir mettre les bonnes données dans les bons graphs
        self.category: Literal["DO", "pH", "Temperature"] = self.title.split("Graph ")[1]

        # Variables de contrôle pour le zoom des données affichées
        self.zoom_value: ctk.IntVar = self.master.master.zoom_value
        self.delta: ctk.IntVar = self.master.master.delta # For the values
        self.graphs: list[FigureCanvasTkAgg] = graphs # Liste des graphiques existants

        # self.ready = ctk.BooleanVar(value=False)

    def initialize_data(self):
        if self.data is None:
            self.app.log_error(ValueError(f"Graph {self.category} data is None")) # Affiche un message d'erreur indiquant que les données du graphique sont nulles
            return

        self.timestamps = self.data["time"]
        self.values = self.data["values"]

        # Obtention des limites de la catégorie (DO, pH, Température)
        self.category_value = get_category_boundaries(self.app, self.category)

        # Obtention des limites supérieure et inférieure (seuils) de la catégorie du graphique
        self.upper_boundary, self.lower_boundary = self.category_value

        self.create_graph()
        

    # Fonction appelée lors du zoom sur le graphique
    def on_zoom(self, event):
        x_min, x_max, y_min, y_max = event.artist.xy
        self.ax.set_xlim(x_min, x_max)
        self.ax.set_ylim(y_min, y_max)
        self.fig.canvas.draw()  # Update the canvas after zoom

    def create_graph(self):
        value_amount = 14400//self.delta.get() if self._type == "4H" else len(self.timestamps)
        x_axis = [i // (self.delta.get() / 60) for i in range(value_amount)] if self._type == "4H" else self.timestamps  # Convert to hours
        data_skip = {"DO":2, "pH":3, "Temperature":5}[self.category]

        # Création du graphique
        ax: plt.Axes
        fig: Figure
        fig, ax = plt.subplots(dpi=self.zoom_value.get()) #dpi = zoom amount
        
        if self._type == "4H":
            # Set major ticks every hour
            def hour_formatter(x, pos):
                """Custom formatter to display hours with "H" suffix"""
                hours = int(x / (self.delta.get())/3)
                return f"{hours}H"
            ax.xaxis.set_major_formatter(hour_formatter)

        ax.set_ylabel(ylabel=self.ylabel, fontsize = 25)
        ax.tick_params(axis='both', which='major', labelsize=25)  # Set tick font size
        ax.grid(True)
        
        plt.plot(x_axis,self.values, color=self.color, linewidth=0.6) # Tracé du graphique

        # Configuration des limites et des seuils sur l'axe des ordonnées
        ax.set_yticks([i for i in range(int(self.lower_boundary)-2, int(self.upper_boundary)+3, data_skip)])
        ax.set_xlim(0, value_amount) # Set x-axis limits based on number of hours
        ax.set_ylim(self.lower_boundary-2, self.upper_boundary+2)
        ax.plot([self.lower_boundary for _ in range(value_amount)], "--", color=PURE_RED)
        ax.plot([self.upper_boundary for _ in range(value_amount)], "--", color=PURE_RED)

        # Création du widget Tkinter pour afficher le graphique
        try:
            graph = FigureCanvasTkAgg(fig, master=self)
            graph.get_tk_widget().place(relx=0, rely=0, relwidth=1, relheight=1)
        except Exception as e:
            return

        # Ajout du graphique dans la liste des graphiques existants
        self.graphs.append(graph)
        if len(self.graphs) > 3:
            plt.close(self.graphs[0].figure)
            del self.graphs[0]
            
        graph.draw()# Mise à jour de l'affichage du graphique
        # self.ready.set(True)

class GraphMenu(Menu):
    def __init__(self, master, title: str, func: Callable):
        super().__init__(master=master, title=title)

        self.get_data = func # Fonction pour obtenir les données du graphique
        self.graphs: list[FigureCanvasTkAgg] = [] # Liste des graphiques existants

        # Variables de contrôle pour le zoom des données affichées
        self.delta = ctk.IntVar(value=30)
        self.zoom_value = ctk.IntVar(value=70)

        self.create_widgets()

    def create_widgets(self) -> None:
        self.loading_frame = ctk.CTkFrame(master=self, corner_radius=0)

        self.loading_label = ctk.CTkLabel(master=self.loading_frame, text="Chargement des données...")
        self.loading_label.place(relx=0.5, rely=0.5, anchor="center")

        self.graph_frame = ctk.CTkFrame(master=self, corner_radius=0)

    # Création des graphiques en fonction du type spécifié 4H ou 24H
    def create_graphs(self, _type: Literal["4H", "24H"]) -> None:
        self._type = _type
        self.graph_frame.place_forget()

        data = self.get_data()

        # Création des graphiques pour DO, pH et Température
        self.graph_do = Graph(
            master=self.graph_frame,
            type=self._type,
            title="Graph DO",
            ylabel="DO \n (mg/L)",
            color=PURE_BLUE,
            graphs=self.graphs,
            data={"time": data["time"], "values": data["values"]["DO"]}
        )
        self.graph_ph = Graph(
            master=self.graph_frame,
            type=self._type,
            title="Graph pH",
            ylabel="pH",
            color=PURE_GREEN,
            graphs=self.graphs,
            data={"time": data["time"], "values": data["values"]["pH"]}
        )
        self.graph_temp = Graph(
            master=self.graph_frame,
            type=self._type,
            title="Graph Temperature",
            ylabel="Température \n (°C)",
            color=DARK_RED,
            graphs=self.graphs,
            data={"time": data["time"], "values": data["values"]["Temperature"]}
        )

        # Placement des sous-frames dans le frame principal
        self.graph_do.place(relx=0.05, rely=0, relwidth=0.9, relheight=0.3)
        self.graph_ph.place(relx=0.05, rely=0.35, relwidth=0.9, relheight=0.3)
        self.graph_temp.place(relx=0.05, rely=0.7, relwidth=0.9, relheight=0.3)

        self.loading_frame.place(relx=0, rely=0, relwidth=1, relheight=1)
        self.update()

        self.graph_do.initialize_data()
        self.graph_ph.initialize_data()
        self.graph_temp.initialize_data()

        _rely = 0.1 if _type == "4H" else 0.05
        _relheight = 0.85 if _type == "4H" else 0.9
        self.after(1000, lambda: self.graph_frame.place(relx=0, rely=_rely, relwidth=1, relheight=_relheight))
        self.update()
class Graph4H(GraphMenu):…

class Graph24H(GraphMenu):
    def __init__(self, master, title: str):
        super().__init__(master=master, title=title, func=self.get_data_for_last_24h)
        self.extra = lambda: self.create_graphs("24H")

    def get_data_for_last_24h(self) -> dict[str, dict[Literal["DO", "pH", "Temperature"], list[int]]]:
        # Fonction pour obtenir les données pour la catégorie spécifiée (DO, pH, Température) sur les 4 dernières heures
        data = {"time": [], "values": {"DO": [], "pH": [], "Temperature": []}}

        loaded_data: dict[str, dict[str, list[float]]] = self.master.load_data(get_data_filenames())
        for day, day_values in loaded_data.items():
            data["time"].append(day)
            for category in ["DO", "pH", "Temperature"]:
                data["values"][category].append(day_values[category])

        return data

我也尝试用这个替换 def get_data_for_last_24H 但它没有产生任何结果:

def get_data_for_last_24h(self) -> dict[str, dict[Literal["DO", "pH", "Temperature"], list[int]]]:
        # Function to get data for the last 24 hours with points every 2 hours
        data = {"time": [], "values": {"DO": [], "pH": [], "Temperature": []}}

        loaded_data: dict[str, dict[str, list[float]]] = self.master.load_data(get_data_filenames())
        for day, day_values in loaded_data.items():
            if "time" in day_values:
                for timestamp in day_values["time"]:
                    # Extract hour from timestamp (assuming format HH:mm:ss)
                    hour = int(timestamp.split(":")[0])
                    # Check if hour is divisible by 2 (represents data point every 2 hours)
                    if hour % 2 == 0:
                        data["time"].append(timestamp)
                        for category in ["DO", "pH", "Temperature"]:
                            data["values"][category].append(day_values[category])
            else:
                # Handle the case where there are no timestamps for this day
                print(f"Warning: No timestamps found for day {day}")
python csv matplotlib matplotlib-widget
1个回答
0
投票

我尝试展示如何使用下面的一些每小时测试数据来格式化这样的图。

每隔一小时采样一次的函数是

.asfreq('2h')
。它只是选择行;它不平均。

enter image description here

大部分日期格式是由

matplotlib.dates.ConciseDateFormatter()
完成的,它具有良好的默认值。为了更紧密地匹配您的格式,我们又花了几行告诉格式化程序将小时渲染为“6H”而不是默认的“06:00”。

可重现的示例

import pandas as pd
import numpy as np
from matplotlib import pyplot as plt

#Data for testing
dates = pd.date_range('1 May 2024', '4 May 2024', freq='h')
df = pd.DataFrame(
    {
        'DO': np.random.uniform(5, 16, dates.size),
        'pH': np.random.uniform(7, 7.5, size=dates.size),
        'Temperature': np.random.uniform(28, 34, size=dates.size)
    },
    index=dates
)
df

#
# Resample down to a 2H frequency - this will keep every 2h data point
#
df_resampled = df.asfreq('2h')
df_resampled

#
# Plot
#
from matplotlib import dates as mdates


#Plot the data and guide lines
plot_col = 'DO'

f, ax = plt.subplots(figsize=(11, 2.5), layout='tight')
ax.plot(
    df_resampled.index, df_resampled[plot_col],
    marker='s', markersize=5, linewidth=1.5, color='dodgerblue', label=plot_col
)
[ax.axhline(y, color='tab:red', linestyle=':', linewidth=1) for y in [7, 13]]

#Set the plot date range
ax.set_xlim(mdates.datestr2num(['1 May 2024', '3 May 2024']))

#Add grids and labels
ax.grid(color='gray', linestyle=':', linewidth=0.5)
ax.set(xlabel='', ylabel=plot_col)

#Configure the x axis ticks and formats
ax.xaxis.set_major_locator(mdates.HourLocator(interval=2))

#Default would format the time as "06:00". OP requires "6H".
formatter = mdates.ConciseDateFormatter(
    locator=ax.xaxis.get_major_locator(),
    formats=['%Y', '%b', '%d', '%-HH', '%-HH', '%S.%f'],  #changed the time to %-HH
    zero_formats=['', '%Y', '%b', '%b-%d', '%H:%M', '%H:%M'] #added ""-%d" to %b
)
ax.xaxis.set_major_formatter(formatter)

#Final formatting
ax.spines[['right', 'top']].set_visible(False)
ax.tick_params(axis='x', rotation=25)
© www.soinside.com 2019 - 2024. All rights reserved.