将时间转换为指定的时间格式并应用于绘图的 y 轴
Convert times to designated time format and apply to y-axis of plotly graph
我目前正在尝试根据文章 An Interactive Web Dashboard with Plotly and Flask.
使用 plotly
和 flask 创建用于 Formula1 分析的网络仪表板
我有 MM:SS:sss 字符串格式的单圈时间(其中 MM 是分钟,sss 是毫秒)并且我尝试(下面的 python 脚本)使用 [= 将其转换为可量化的值12=] 这样我就可以绘制它们并对其进行操作(例如,找到驾驶员在多圈内的平均时间)。但是,当我尝试绘制 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))
您需要将 timedelta
的数字内部值转换为与其存储的时间单位不同的时间单位,即 days
, seconds
,以及microseconds
.
既然你说你不能自己将它们转换成字符串,一个潜在的解决方法可能是将它们转换成一个 timedelta
subclass ,它会按照你想要的方式将自己转换成一个字符串。
要记住的一件事是 timedelta
可能包含巨大的值,必须以某种方式处理这些值——所以只是说你想要 "MM:SS.sss" 格式会忽略事实上,理论上也可能涉及数天和数小时。下面的函数计算它们,但仅在它们非零时显示它们的值。
下面的代码定义了一个新的 MyTimeDelta
subclass 并使用它。我已经将 subclass' __str__()
方法定义为 return 所需格式的字符串。每当新 class 的实例被转换为字符串时,都会使用这个以前的函数,但是 class 作为一个整体仍然是“数字的”,就像它的基础 class 一样。 subclass' __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)
- 您将文本转换为分析表示是正确的。我也使用了 Timedelta。在某些方面,使用纳秒会更简单
- 您还需要转换回轴刻度和悬停文本。我为此使用了一个效用函数
- 所有这些结合在一起,您可以创建 plotly 正确且人类可读的单圈时间图 ;-)
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)
}
)
我目前正在尝试根据文章 An Interactive Web Dashboard with Plotly and Flask.
使用plotly
和 flask 创建用于 Formula1 分析的网络仪表板
我有 MM:SS:sss 字符串格式的单圈时间(其中 MM 是分钟,sss 是毫秒)并且我尝试(下面的 python 脚本)使用 [= 将其转换为可量化的值12=] 这样我就可以绘制它们并对其进行操作(例如,找到驾驶员在多圈内的平均时间)。但是,当我尝试绘制 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))
您需要将 timedelta
的数字内部值转换为与其存储的时间单位不同的时间单位,即 days
, seconds
,以及microseconds
.
既然你说你不能自己将它们转换成字符串,一个潜在的解决方法可能是将它们转换成一个 timedelta
subclass ,它会按照你想要的方式将自己转换成一个字符串。
要记住的一件事是 timedelta
可能包含巨大的值,必须以某种方式处理这些值——所以只是说你想要 "MM:SS.sss" 格式会忽略事实上,理论上也可能涉及数天和数小时。下面的函数计算它们,但仅在它们非零时显示它们的值。
下面的代码定义了一个新的 MyTimeDelta
subclass 并使用它。我已经将 subclass' __str__()
方法定义为 return 所需格式的字符串。每当新 class 的实例被转换为字符串时,都会使用这个以前的函数,但是 class 作为一个整体仍然是“数字的”,就像它的基础 class 一样。 subclass' __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)
- 您将文本转换为分析表示是正确的。我也使用了 Timedelta。在某些方面,使用纳秒会更简单
- 您还需要转换回轴刻度和悬停文本。我为此使用了一个效用函数
- 所有这些结合在一起,您可以创建 plotly 正确且人类可读的单圈时间图 ;-)
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)
}
)