将时间转换为指定的时间格式并应用于绘图仪的坐标轴

2024-05-18 04:27:08 发布

您现在位置:Python中文网/ 问答频道 /正文

我目前正试图根据文章An Interactive Web Dashboard with Plotly and Flask使用plotly和flask在公式1中创建用于分析的web仪表板

我有MM:SS:sss字符串格式的圈数(其中MM是分钟,sss是毫秒),我尝试(下面的python脚本)使用datetime.timedelta将其转换为可量化的值,以便我能够绘制它们并对其进行操作(例如,找到一个驱动程序在若干圈上的平均时间)。但是,当我尝试在plotly中绘制timedelta对象时,它们以微秒为单位显示

是否可以以指定的时间格式创建timedelta对象并对其进行量化,以便plotly正确地绘制它们

from datetime import timedelta

times = ["1:23.921", "1:24.690", "1:24.790"]

# convert to timedelta object
def string_con(string_time):
    new_time = timedelta(minutes=int(string_time.split(
        ":")[0]), seconds=int((string_time.split(":")[1]).split(".")[0]),
        milliseconds=int((string_time.split(":")[1]).split(".")[1]))
    return new_time


# compute average pace using timedelta objects
def average_pace(laps):
    laps = list(map(string_con, laps))
    return (sum(laps, timedelta(0))/len(laps))

print(average_pace(times))

Tags: datetimestringtime格式时间绘制plotlytimedelta
2条回答
  • 将文本转换为分析表达是正确的。我也使用了Timedelta。在某些方面,使用纳秒会更简单
  • 您还需要转换回轴标记和悬停文本。我使用了一个实用函数来实现这一点
  • 这一切结合在一起,您可以创建正确且可读的圈速曲线图;-)
import requests
import pandas as pd
import plotly.express as px

# get some lap timing data
df = pd.concat([
        pd.json_normalize(requests.get(f"https://ergast.com/api/f1/2021/7/laps/{l}.json").json()
                          ["MRData"]["RaceTable"]["Races"][0]["Laps"][0]["Timings"]
        ).assign(lap=l)
        for l in range(1, 25)
    ]).reset_index(drop=True)
# convert to timedelta...
df["time"] = (
    df["time"]
    .str.extract(r"(?P<minute>[0-9]+):(?P<sec>[0-9]+).(?P<milli>[0-9]+)")
    .apply(
        lambda r: pd.Timestamp(year=1970,month=1,day=1,
                               minute=int(r.minute),second=int(r.sec),microsecond=int(r.milli) * 10 ** 3,
        ),
        axis=1,
    )
    - pd.to_datetime("1-jan-1970").replace(hour=0, minute=0, second=0, microsecond=0)
)

# utility build display string from nanoseconds
def strfdelta(t, fmt="{minutes:02d}:{seconds:02d}.{milli:03d}"):
    d = {}
    d["minutes"], rem = divmod(t, 10 ** 9 * 60)
    d["seconds"], d["milli"] = divmod(rem, 10 ** 9)
    d["milli"] = d["milli"] // 10**6
    return fmt.format(**d)

# build a figure with lap times data...  NB use of hover_name for formatted time
fig = px.scatter(
    df,
    x="lap",
    y="time",
    color="driverId",
    hover_name=df["time"].astype(int).apply(strfdelta),
    hover_data={"time":False},
    size=df.groupby("lap")["time"].transform(
        lambda s: s.rank(ascending=True).eq(1).astype(int)
    ),
)
# make figure more interesting... add best/worst and mean lap times...
fig.add_traces(
    px.line(
        df.groupby("lap")
        .agg(
            avg=("time", lambda s: s.mean()),
            min=("time", lambda s: s.min()),
            max=("time", lambda s: s.max()),
        )
        .reset_index(),
        x="lap",
        y=["avg", "min", "max"],
    ).data
)

# fix up tick labels
ticks = pd.Series(range(df["time"].astype(int).min() - 10 ** 10,df["time"].astype(int).max(),10 ** 10,))
fig.update_layout(
    yaxis={
        "range": [
            df["time"].astype(int).min() - 10 ** 10,
            df["time"].astype(int).max(),
        ],
        "tickmode": "array",
        "tickvals": ticks,
        "ticktext": ticks.apply(strfdelta)
    }
)



enter image description here

您需要将一个timedelta的内部数值转换为不同的时间单位,这些时间单位与它存储的时间单位不同,分别是dayssecondsmicroseconds

既然您说自己无法将它们转换为字符串,那么一个潜在的解决方法可能是将它们转换为timedelta子类,该子类可以按照您想要的方式将自身转换为字符串

需要记住的一件事是timedelta可能包含巨大的值,必须以某种方式进行处理-因此,仅仅说您想要“MM:SS.sss”格式忽略了一个事实,即理论上也可能涉及数天和数小时。下面的函数计算它们,但仅在它们非零时显示它们的值

下面的代码定义了一个新的MyTimeDelta子类并使用它。我已经定义了子类“__str__()方法以返回所需格式的字符串。现在,每当新类的实例转换为字符串时,都会使用前一个函数,但该类作为一个整体仍然像它的基类一样是“数值的”。子类“__str__()方法使用了我添加的名为_convert_units()的私有助手方法

from datetime import timedelta

class MyTimeDelta(timedelta):
    @classmethod
    def from_another(cls, other):
        if not isinstance(other, timedelta):
            raise TypeError('unsupported type')
        return cls(days=other.days, seconds=other.seconds, microseconds=other.microseconds)

    def __str__(self):
        """ Format a timedelta into this format D:H:MM:SS.sss """
        res = []
        days, hours, minutes, seconds = self._convert_units()
        if days:
            res.append(f'{days}:')
        if hours or days:
            res.append(f'{hours}:')
        if minutes or hours or days:
            res.append(f'{minutes:02d}:')
        res.append(f'{seconds}')
        return ''.join(res)

    def _convert_units(self):
        """ Convert a timedelta to days, hours, minutes, & seconds."""
        days = self.days
        hours, remainder = divmod(self.seconds, 3600)
        minutes, seconds = divmod(remainder, 60)
        seconds += self.microseconds / 1e6
        return days, hours, minutes, seconds

times = ["1:23.921", "1:24.690", "1:24.790"]

def string_con(string_time):
    """ Convert string_time to timedelta object. """
    split_time = string_time.split(":")
    split_secs = split_time[1].split(".")
    mins, secs, ms = map(int, (split_time[0], split_secs[0], split_secs[1]))
    return timedelta(minutes=mins, seconds=secs, milliseconds=ms)

def average_pace(laps):
    """ Compute average pace using timedelta objects. """
    laps = [string_con(lap) for lap in laps]
    return sum(laps, timedelta(0)) / len(laps)


avg = MyTimeDelta.from_another(average_pace(times))
print(f'{avg.days=}, {avg.seconds=}, {avg.microseconds=}')
print(avg)

相关问题 更多 >