PAYATHON 2026

Plotly 合并多个数据源后折线异常,如何正确处理时间轴?

数据小许

先把横轴数据转换为时间类型,再对每条曲线按时间排序。如果要在同一条“当天时间”轴上比较不同日期的数据,还需要把日期统一到一个参考日。

现有代码中,建议先检查 last_updated:正则表达式把完整时间戳截成了 "00:01" 这样的字符串。Plotly 可能将这些值识别为分类标签,使横轴顺序与 DataFrame 中的时间顺序不一致。仅凭当前数据片段还无法确认全部原因,不过这与“采样时刻不一致时才出现异常”的现象吻合。

为什么 DataFrame 已排序,图上仍会折返?

sort_values() 改变的是数据行顺序。在分类轴上,每个标签的位置还取决于图表的类别顺序。

例如,第一条曲线包含这些时间字符串:

00:01, 00:06, 00:11

第二条曲线包含:

00:01, 00:07, 00:11

假设横轴是按类别首次出现的顺序排列的分类轴。第一条曲线先建立了三个类别,第二条曲线新增的 00:07 就可能被追加到后面。这样一来,第二条曲线虽然按 00:01 → 00:07 → 00:11 连接,图上的位置却可能先向右、再向左。

因此,异常点不一定出现在 DataFrame 末尾。数据行的顺序和横轴类别的位置需要分开看。

将时间转换为 datetime,并统一比较日期

示例数据包含不同日期。如果要比较这些日期里相同钟点的排队时间,可以保留原始时间戳,新增一个 plot_time 列用于绘图。

下面的代码假设 CSV 中的 last_updated 都是完整时间戳,且具有相同的 +09:00 偏移,这与原正则表达式的预期一致。请重新加载原始 CSV,替换原先截取 HH:MM 的逻辑。

先在类的初始化方法中设置:

self.frame = pd.DataFrame()

然后替换两个方法:

import os

import pandas as pd
import plotly.express as px


def load_data(self, date, ride_name, park_name):
    filename = os.path.join(
        os.environ["SAVE_PATH"],
        park_name,
        str(date),
        f"{date}_{ride_name}.csv",
    )
    df = pd.read_csv(filename, sep=",", skipinitialspace=True)

    timestamps = pd.to_datetime(df["last_updated"], errors="raise")
    df["last_updated"] = timestamps

    # 保留当天经过的时长,包括秒,并统一到同一个参考日。
    time_of_day = timestamps - timestamps.dt.normalize()
    df["plot_time"] = pd.Timestamp("2000-01-01") + time_of_day
    df["ride"] = f"{date}-{ride_name}"
    return df


def update_data_frame(self, name, date):
    name = name.replace(" ", "_")
    df = self.load_data(date, name, "tds")
    self.frame = pd.concat(
        [self.frame, df],
        axis=0,
        ignore_index=True,
    )

    self.frame = self.frame.sort_values(
        by=["ride", "plot_time"],
        kind="stable",
        ignore_index=True,
    )

    self.fig = px.line(
        self.frame,
        x="plot_time",
        y="wait_time",
        color="ride",
        title="Wait Times",
        labels={
            "plot_time": "Last Updated",
            "wait_time": "Wait Time (min)",
        },
    )
    self.fig.update_xaxes(type="date", tickformat="%H:%M")

这里的 2000-01-01 是用于对齐钟点的参考日。原始日期仍保存在 last_updated 中,ride 也继续区分日期和游乐设施。

tickformat="%H:%M" 只控制刻度显示,不会删除数据中的秒数。不同曲线也不必在相同时刻采样:真实时间轴可以正确放置 00:06 和 00:07,无需为了修复折返而强行对齐采样点。

如果需要展示真实日期顺序

如果要查看跨日期的连续变化,可以直接使用转换后的 last_updated 绘图,无需创建统一参考日:

self.frame = self.frame.sort_values(
    ["ride", "last_updated"],
    kind="stable",
)

self.fig = px.line(
    self.frame,
    x="last_updated",
    y="wait_time",
    color="ride",
)
self.fig.update_xaxes(type="date", tickformat="%m-%d %H:%M")

不同日期会位于横轴上的不同位置。是否按钟点重叠比较,取决于仪表板的用途。

修改后还需要检查什么?

可以检查绘图列的类型,并查看每条曲线实际收到的横轴数据:

print(self.frame[["last_updated", "plot_time"]].dtypes)

for trace in self.fig.data:
    print(trace.name, list(trace.x[:5]))

绘图列应为 datetime 类型。只给 "HH:MM" 字符串设置 type="date",不能代替时间解析。

同一条曲线在同一时刻有多条记录时,仍可能出现竖线;反复加载同一个 CSV 也可能造成数据重复。是否去重或聚合,应先弄清这些记录的含义,不能直接删除。

如果文件中混有不同时区,应先明确仪表板使用哪个时区,转换到该时区后再提取当天时间。上面的对齐方法以时间戳具有相同的 +09:00 偏移为前提。跨午夜的连续运行记录也要单独考虑,因为按钟点对齐后,午夜后的数据会回到横轴起点。

备注:内容仅供参考。