示例#1
0
def generate_results_table(yd_results, yd_fastest_lap_data, year_results,
                           year_fastest_lap_data, driver_id):
    """
    Table of results at each race, including quali position, finish position (or reason for DNF), time, gap to leader,
    fastest lap time and gap to fastest lap (of all drivers), average lap time and gap to fastest average lap time
    (of all drivers)
    :param yd_results: YD results
    :param yd_fastest_lap_data: YD fastest lap data
    :param year_results: Year results
    :param year_fastest_lap_data: Year fastest lap data
    :param driver_id: Driver ID
    :return: Results table
    """
    logging.info("Generating results table")
    source = pd.DataFrame(columns=[
        "race_name", "constructor_name", "quali_pos_str", "finish_pos_str",
        "time_str", "fastest_lap_time_str", "avg_lap_time_str"
    ])
    for idx, results_row in yd_results.iterrows():
        rid = results_row["raceId"]
        race_results = year_results[year_results["raceId"] == rid]
        race_fastest_lap_data = year_fastest_lap_data[
            year_fastest_lap_data["raceId"] == rid]
        race_driver_fastest_lap_data = yd_fastest_lap_data[
            yd_fastest_lap_data["raceId"] == rid]
        race_name = get_race_name(rid)
        constructor_name = get_constructor_name(results_row["constructorId"])
        quali_pos_str = int_to_ordinal(results_row["grid"])
        finish_pos = str(results_row["positionOrder"])
        status_id = results_row["statusId"]
        finish_pos_str, finish_pos = result_to_str(finish_pos, status_id)
        classification = get_status_classification(status_id)
        time = results_row["milliseconds"]
        winner = race_results[race_results["positionOrder"] == 1]
        if winner.shape[0] > 0 and winner["driverId"].values[0] != driver_id \
                and not np.isnan(time) and not np.isnan(results_row["position"]):
            time_gap = millis_to_str(time - winner["milliseconds"].values[0])
            time_str = millis_to_str(time) + " (+" + time_gap + ")"
            if status_id != 1 and classification == "finished":
                time_str = millis_to_str(
                    time) + " (+" + time_gap + ", " + status.loc[
                        status_id, "status"] + ")"
        elif finish_pos == 1:
            time_str = millis_to_str(time)
        else:
            time_str = "~"
        if race_driver_fastest_lap_data.shape[0] > 0:
            fastest_lap_time = race_driver_fastest_lap_data[
                "fastest_lap_time_millis"].values[0]
            fastest_lap_time_str = millis_to_str(fastest_lap_time)
            if race_driver_fastest_lap_data["rank"].values[0] == " 1":
                fastest_lap_time_str = fastest_lap_time_str + " (Fastest)"
            else:
                fastest_time = race_fastest_lap_data[
                    race_fastest_lap_data["rank"] ==
                    " 1"]["fastest_lap_time_millis"]
                if fastest_time.shape[0] > 0 and not np.isnan(
                        fastest_lap_time):
                    fastest_time = fastest_time.values[0]
                    fastest_gap = millis_to_str(fastest_lap_time -
                                                fastest_time)
                    fastest_lap_time_str = millis_to_str(
                        fastest_lap_time) + " (+" + fastest_gap + ")"
            if fastest_lap_time_str == "":
                fastest_lap_time_str = "~"
            fastest_avg_idx = race_fastest_lap_data[
                "avg_lap_time_millis"].idxmin()
            avg_lap_time = race_driver_fastest_lap_data[
                "avg_lap_time_millis"].values[0]
            if np.isnan(avg_lap_time):
                avg_lap_time_str = "~"
            elif race_fastest_lap_data.loc[
                    fastest_avg_idx,
                    "driver_id"] == driver_id or np.isnan(avg_lap_time):
                avg_lap_time_str = millis_to_str(
                    avg_lap_time) + " (Fastest Avg.)"
            else:
                fastest_avg_time = race_fastest_lap_data.loc[
                    fastest_avg_idx, "avg_lap_time_millis"]
                avg_gap = millis_to_str(avg_lap_time - fastest_avg_time)
                avg_lap_time_str = millis_to_str(
                    avg_lap_time) + " (+" + avg_gap + ")"
        else:
            fastest_lap_time_str = "~"
            avg_lap_time_str = "~"
        source = source.append(
            {
                "race_name": race_name,
                "constructor_name": constructor_name,
                "quali_pos_str": quali_pos_str,
                "finish_pos_str": finish_pos_str,
                "time_str": time_str,
                "fastest_lap_time_str": fastest_lap_time_str,
                "avg_lap_time_str": avg_lap_time_str
            },
            ignore_index=True)

    results_columns = [
        TableColumn(field="race_name", title="Race Name", width=100),
        TableColumn(field="constructor_name", title="Constructor", width=100),
        TableColumn(field="quali_pos_str", title="Grid Pos.", width=75),
        TableColumn(field="finish_pos_str", title="Finish Pos.", width=75),
        TableColumn(field="time_str", title="Time", width=100),
        TableColumn(field="fastest_lap_time_str",
                    title="Fastest Lap Time",
                    width=75),
        TableColumn(field="avg_lap_time_str", title="Avg. Lap Time", width=75),
    ]
    results_table = DataTable(source=ColumnDataSource(data=source),
                              columns=results_columns,
                              index_position=None,
                              height=27 * yd_results.shape[0])
    title = Div(
        text=
        f"<h2><b>Results for each race</b></h2><br><i>The fastest lap time and average lap time gaps "
        f"shown are calculated based on the gap to the fastest of all drivers and fastest average of "
        f"all drivers in that race respectively.</i>")
    return column(
        [title, row([results_table], sizing_mode="stretch_width")],
        sizing_mode="stretch_width")
示例#2
0
def generate_stats_layout(ycd_results,
                          ycd_pit_stop_data,
                          ycd_fastest_lap_data,
                          year_driver_standings,
                          race_results,
                          quali_source,
                          race_id,
                          circuit_id,
                          driver_id,
                          download_image=True):
    """
    Stats div including:
    - Location
    - Date
    - Weather
    - Rating
    - Constructor
    - Qualifying position and time
    - Laps
    - Fastest lap time along with rank
    - Average lap time
    - Basic teammate info (i.e. teammate finish in 5th with an average lap time of 1:15.125)
    - Finish position
    - Finish time
    - Points scored
    - Num pit stops
    - WDC impact (WDC place before, WDC after)
    :param ycd_results: YCD results
    :param ycd_pit_stop_data: YCD pit stop data
    :param ycd_fastest_lap_data: YCD fastest lap data
    :param year_driver_standings: YCD driver standings
    :param race_results: Race results
    :param quali_source: Quali source
    :param race_id: Race ID
    :param circuit_id: Circuit ID
    :param driver_id: Driver ID
    :param download_image: Whether to actually download the image
    :return: Stats layout
    """
    logging.info("Generating race stats layout")
    # Track image
    if download_image:
        image_url = str(circuits.loc[circuit_id, "imgUrl"])
        image_view = plot_image_url(image_url)
        disclaimer = Div(
            text="The image is of the current configuration of the track.")
        image_view = column([image_view, disclaimer],
                            sizing_mode="stretch_both")
    else:
        image_view = Div()

    # Race info
    race = races.loc[race_id]
    round_num = race["round"]
    circuit = circuits.loc[circuit_id]
    date = race["datetime"].split(" ")
    if len(date) > 0:
        date_str = date[0]
    else:
        date_str = race["datetime"]
    location_str = circuit["location"] + ", " + circuit["country"]
    circuit_str = circuit["name"] + " (" + location_str + ")"
    weather = race["weather"]
    if weather is None or weather == "":
        weather_str = ""
    else:
        weather_str = str(weather).title()
    sc = race["SCLaps"]
    if np.isnan(sc):
        sc_str = ""
    else:
        sc_str = str(int(sc)) + " laps under safety car"
    rating = race["rating"]
    if np.isnan(rating):
        rating_str = ""
    else:
        rating_str = str(round(rating, 1)) + " / 10"
    ycd_driver_standings = year_driver_standings[
        (year_driver_standings["raceId"] == race_id)
        & (year_driver_standings["driverId"] == driver_id)]
    ycd_driver_standings_prev = year_driver_standings[
        (year_driver_standings["raceId"] == race_id - 1)
        & (year_driver_standings["driverId"] == driver_id)]
    if round_num == 1 and ycd_driver_standings.shape[0] > 0:
        wdc_impact_str = int_to_ordinal(
            ycd_driver_standings["positionText"].values[0])
    elif round_num > 1 and ycd_driver_standings.shape[
            0] > 0 and ycd_driver_standings_prev.shape[0] > 0:
        wdc_impact_str = "from " + int_to_ordinal(ycd_driver_standings_prev["position"].values[0]) + " to " + \
                         int_to_ordinal(ycd_driver_standings["position"].values[0])
    else:
        wdc_impact_str = ""
    if ycd_pit_stop_data.shape[0] > 0:
        num_pit_stops_str = str(ycd_pit_stop_data.shape[0])
    else:
        num_pit_stops_str = ""
    if ycd_results.shape[0] > 0:
        ycd_results_row = ycd_results.iloc[0]
        constructor_id = ycd_results_row["constructorId"]
        constructor_str = get_constructor_name(constructor_id)
        grid_str = int_to_ordinal(ycd_results_row["grid"]).strip()
        fp_str, _ = result_to_str(ycd_results_row["positionOrder"],
                                  ycd_results_row["statusId"])
        fp_str = fp_str.strip()
        laps_str = str(ycd_results_row["laps"])
        runtime_str = millis_to_str(ycd_results_row["milliseconds"]).strip()
        points = ycd_results_row["points"]
        if abs(int(points) - points) < 0.01:
            points = int(points)
        points_str = str(points)
        teammates = set(
            race_results[race_results["constructorId"] ==
                         constructor_id]["driverId"].values) - {driver_id}
        teammate_strs = []
        for teammate_did in teammates:
            teammate_result = race_results[race_results["driverId"] ==
                                           teammate_did]
            if teammate_result.shape[0] > 0:
                tm_result_row = teammate_result.iloc[0]
                tm_name = get_driver_name(teammate_did)
                tm_fp_str, _ = result_to_str(tm_result_row["positionOrder"],
                                             tm_result_row["statusId"])
                tm_time_str = millis_to_str(tm_result_row["milliseconds"])
                if "ret" in tm_fp_str.lower():
                    teammate_strs.append(tm_name + " " + tm_fp_str)
                else:
                    teammate_strs.append(tm_name + " finished " +
                                         tm_fp_str.strip() + " (" +
                                         tm_time_str.strip() + ")")
        teammate_str = ", ".join(teammate_strs)
    else:
        constructor_str = ""
        grid_str = ""
        fp_str = ""
        laps_str = ""
        runtime_str = ""
        points_str = ""
        teammate_str = ""
    ycd_quali_source = quali_source[quali_source["driver_id"] == driver_id]
    if ycd_quali_source.shape[0] > 0:
        ycd_quali_row = ycd_quali_source.iloc[0]
        quali_pos = ycd_quali_row["quali_position"]
        quali_pos_str = int_to_ordinal(quali_pos).strip()
        quali_time_str = ""
        if "q1" in ycd_quali_source.columns and ycd_quali_row["q1"] != "~":
            quali_time_str = ycd_quali_row["q1"]
        if "q2" in ycd_quali_source.columns and ycd_quali_row["q2"] != "~":
            quali_time_str = ycd_quali_row["q2"]
        if "q3" in ycd_quali_source.columns and ycd_quali_row["q3"] != "~":
            quali_time_str = ycd_quali_row["q3"]
        quali_time_str = quali_time_str.strip()
    else:
        quali_pos_str = ""
        quali_time_str = ""
    if ycd_fastest_lap_data.shape[0] > 0:
        ycd_fastest_lap_data_row = ycd_fastest_lap_data.iloc[0]
        if np.isnan(ycd_fastest_lap_data_row["fastest_lap_time_millis"]):
            fastest_lap_str = ""
        else:
            fastest_lap_str = ycd_fastest_lap_data_row["fastest_lap_time_str"] + " (" + \
                              int_to_ordinal(ycd_fastest_lap_data_row["rank"]).strip() + " fastest this race)"
        avg_lap_time_str = millis_to_str(
            ycd_fastest_lap_data_row["avg_lap_time_millis"])
    else:
        fastest_lap_str = ""
        avg_lap_time_str = ""

    header_template = """
    <h2 style="text-align: center;"><b>{}</b></h2>
    """

    template = """
    <pre><b>{}</b> {}<br></pre>
    """

    race_name = get_race_name(race_id, include_year=True)
    driver_name = get_driver_name(driver_id)
    ycd_stats = header_template.format(driver_name + " at the " + race_name)

    ycd_stats += template.format("Circuit Name: ".ljust(22), circuit_str)
    ycd_stats += template.format("Date: ".ljust(22), date_str)
    if weather_str != "" and weather_str.lower() != "nan":
        ycd_stats += template.format("Weather: ".ljust(22), weather_str)
    if not np.isnan(rating):
        ycd_stats += template.format("Rating: ".ljust(22), rating_str)
    if not np.isnan(sc):
        ycd_stats += template.format("Safety Car Laps: ".ljust(22), sc_str)
    if wdc_impact_str != "":
        if round_num == 1:
            ycd_stats += template.format("WDC Position: ".ljust(22),
                                         wdc_impact_str)
        else:
            ycd_stats += template.format("WDC Impact: ".ljust(22),
                                         wdc_impact_str)
    if ycd_pit_stop_data.shape[0] > 0:
        ycd_stats += template.format("Num Pit Stops: ".ljust(22),
                                     num_pit_stops_str)
    if quali_pos_str != "":
        ycd_stats += template.format("Qualifying Position: ".ljust(22),
                                     quali_pos_str)
    if quali_time_str != "":
        ycd_stats += template.format("Qualifying Time: ".ljust(22),
                                     quali_time_str)
    if ycd_results.shape[0] > 0:
        ycd_stats += template.format("Constructor: ".ljust(22),
                                     constructor_str)
        ycd_stats += template.format("Grid Position: ".ljust(22), grid_str)
        ycd_stats += template.format("Finish Position: ".ljust(22), fp_str)
        ycd_stats += template.format("Num Laps: ".ljust(22), laps_str)
        ycd_stats += template.format("Race Time: ".ljust(22), runtime_str)
        ycd_stats += template.format("Points Earned: ".ljust(22), points_str)
        ycd_stats += template.format("Teammate(s): ".ljust(22), teammate_str)
    if ycd_fastest_lap_data.shape[0] > 0:
        if fastest_lap_str != "":
            ycd_stats += template.format("Fastest Lap Time: ".ljust(22),
                                         fastest_lap_str)
        ycd_stats += template.format("Avg. Lap Time: ".ljust(22),
                                     avg_lap_time_str)

    divider = vdivider()
    return row([image_view, divider, Div(text=ycd_stats)],
               sizing_mode="stretch_both")
示例#3
0
def generate_circuit_results_table(circuit_years, circuit_races, circuit_results, circuit_qualifying,
                                   circuit_fastest_lap_data):
    """
    Show results for every race at that circuit, including polesetting and time, winner and time, 2nd and 3rd, and
    fastest lap if that data is available
    :param circuit_years: Circuit years
    :param circuit_races: Circuit races
    :param circuit_results: Circuit results
    :param circuit_qualifying: Circuit qualifying
    :param circuit_fastest_lap_data: Circuit fastest lap data
    :return: Table layout
    """
    logging.info("Generating circuit results table")
    source = pd.DataFrame(columns=["year", "laps", "pole", "fastest_lap", "p1", "p2", "p3"])
    for year in circuit_years:
        race = circuit_races[circuit_races["year"] == year]
        rid = race.index.values[0]
        results = circuit_results[circuit_results["raceId"] == rid]

        # Qualifying
        if results.shape[0] == 0:
            continue
        pole_row = results[results["grid"] == 1].iloc[0]
        pole_did = pole_row["driverId"]
        pole_name = get_driver_name(pole_did)
        quali = circuit_qualifying[(circuit_qualifying["raceId"] == rid) & (circuit_qualifying["driverId"] == pole_did)]
        quali = quali
        pole_time = ""
        if quali.shape[0] > 0:
            quali = quali.iloc[0]
            if not np.isnan(quali["q1"]):
                pole_time = millis_to_str(quali["q1"])
            if not np.isnan(quali["q2"]):
                pole_time = millis_to_str(quali["q2"])
            if not np.isnan(quali["q3"]):
                pole_time = millis_to_str(quali["q3"])
            pole_str = pole_name + " (" + pole_time + ")"
        else:
            pole_str = pole_name

        # Fastest lap
        fastest = circuit_fastest_lap_data[circuit_fastest_lap_data["raceId"] == rid]
        fastest = fastest[fastest["rank"] == " 1"]
        if fastest.shape[0] > 0:
            fastest = fastest.iloc[0]
            fastest_name = get_driver_name(fastest["driver_id"])
            fastest_time = fastest["fastest_lap_time_str"]
            fastest_str = fastest_name + " (" + fastest_time + ")"
        else:
            fastest_str = ""

        # Winner and num laps
        win = results[results["position"] == 1].iloc[0]
        winner_name = get_driver_name(win["driverId"])
        winner_time = millis_to_str(win["milliseconds"])
        winner_constructor = get_constructor_name(win["constructorId"])
        num_laps = win["laps"]

        # P2 and P3
        p2_name = results[results["position"] == 2]["driverId"]
        p3_name = results[results["position"] == 3]["driverId"]

        if p2_name.shape[0] > 0:
            p2_name = get_driver_name(p2_name.values[0])
        else:
            p2_name = ""
        if p3_name.shape[0] > 0:
            p3_name = get_driver_name(p3_name.values[0])
        else:
            p3_name = ""

        source = source.append({
            "year": year,
            "laps": num_laps,
            "pole": pole_str,
            "fastest_lap": fastest_str,
            "p1": winner_name + " (" + winner_time + "), " + winner_constructor,
            "p2": p2_name,
            "p3": p3_name
        }, ignore_index=True)

    title_div = Div(text="<h2><b>Circuit Results</b></h2>")

    source = source.sort_values(by="year", ascending=False)
    column_source = ColumnDataSource(data=source)
    results_columns = [
        TableColumn(field="year", title="Year", width=50),
        TableColumn(field="laps", title="Laps", width=50),
        TableColumn(field="pole", title="Pole Position", width=200),
        TableColumn(field="p1", title="First", width=300),
        TableColumn(field="p2", title="Second", width=150),
        TableColumn(field="p3", title="Third", width=150),
    ]
    if source["fastest_lap"].isna().sum() + source[source["fastest_lap"].str.match("")].shape[0] < source.shape[0]:
        results_columns.insert(3, TableColumn(field="fastest_lap", title="Fastest Lap", width=200))
    fast_table = DataTable(source=column_source, columns=results_columns, index_position=None, min_height=530)
    fast_row = row([fast_table], sizing_mode="stretch_width")

    return column([title_div, fast_row], sizing_mode="stretch_width")
示例#4
0
def generate_times_plot(circuit_years, circuit_quali, circuit_fastest_lap_data, circuit_races, circuit_results,
                        circuit_id):
    """
    Plot quali, fastest lap, and average lap times vs year along with rating vs year
    :param circuit_years: Circuit years
    :param circuit_quali: Circuit quali
    :param circuit_fastest_lap_data: Circuit fastest lap data
    :param circuit_races: Circuit races
    :param circuit_results: Circuit results
    :param circuit_id: Circuit ID
    :return: Times plot layout
    """
    logging.info("Generating times plot")
    if circuit_quali.shape[0] == 0 and (circuit_fastest_lap_data.shape[0] == 0 or
                                        circuit_fastest_lap_data["avg_lap_time_millis"].isna().sum() ==
                                        circuit_fastest_lap_data["avg_lap_time_millis"].shape[0]):
        return Div(text="")
    source = pd.DataFrame(columns=["year",
                                   "quali_time", "quali_time_str",
                                   "fastest_lap_time", "fastest_lap_str",
                                   "avg_lap_time", "avg_lap_str",
                                   "rating"])

    for year in circuit_years:
        race = circuit_races[circuit_races["year"] == year]
        rid = race.index.values[0]

        # Qualifying
        year_quali = circuit_quali[circuit_quali["raceId"] == rid]
        quali_times = year_quali["q1"].append(year_quali["q2"].append(year_quali["q3"]))
        if quali_times.shape[0] == 0 or np.isnan(quali_times.idxmin()):
            quali_millis = np.nan
            quali_str = ""
        else:
            idxmin = int(quali_times.idxmin())
            quali_name = get_driver_name(year_quali.loc[idxmin, "driverId"])
            quali_millis = int(quali_times.loc[idxmin].min())
            quali_str = millis_to_str(quali_millis) + " by " + quali_name

        # Fastest and average lap
        year_fastest_lap_data = circuit_fastest_lap_data[circuit_fastest_lap_data["raceId"] == rid]
        if year_fastest_lap_data.shape[0] == 0:
            avg_lap_millis = np.nan
            avg_lap_str = ""
        else:
            avg_lap_millis = int(year_fastest_lap_data["avg_lap_time_millis"].mean())
            avg_lap_str = millis_to_str(avg_lap_millis)
        if year_fastest_lap_data.shape[0] == 0 or \
                year_fastest_lap_data["fastest_lap_time_millis"].isna().sum() == year_fastest_lap_data.shape[0]:
            fastest_lap_millis = np.nan
            fastest_lap_str = ""
        else:
            idxmin = int(year_fastest_lap_data["fastest_lap_time_millis"].idxmin())
            fastest_lap_name = get_driver_name(year_fastest_lap_data.loc[idxmin, "driver_id"])
            fastest_lap_millis = int(year_fastest_lap_data.loc[idxmin, "fastest_lap_time_millis"])
            fastest_lap_str = millis_to_str(fastest_lap_millis) + " by " + fastest_lap_name

        source = source.append({
            "year": year,
            "quali_time": quali_millis,
            "quali_time_str": quali_str,
            "fastest_lap_time": fastest_lap_millis,
            "fastest_lap_str": fastest_lap_str,
            "avg_lap_time": avg_lap_millis,
            "avg_lap_str": avg_lap_str,
            "rating": race["rating"].values[0],
        }, ignore_index=True)

    circuit_name = get_circuit_name(circuit_id)
    min_time = min(source["fastest_lap_time"].min(), source["avg_lap_time"].min(), source["quali_time"].min()) - 5000
    max_time = max(source["fastest_lap_time"].max(), source["avg_lap_time"].max(), source["quali_time"].max()) + 5000
    start = pd.to_datetime(min_time, unit="ms")
    end = pd.to_datetime(max_time, unit="ms")
    if pd.isna(start) or pd.isna(end):
        min_time = source["avg_lap_time"].min() - 5000
        max_time = source["avg_lap_time"].max() + 5000
        start = pd.to_datetime(min_time, unit="ms")
        end = pd.to_datetime(max_time, unit="ms")

    # Scale rating so that a 0=min_time, 10=max_time
    source["rating_scaled"] = (max_time - min_time) * (source["rating"] / 10) + min_time
    source["rating"] = source["rating"].fillna("")

    min_year = np.min(circuit_years)
    max_year = np.max(circuit_years)
    if min_year == max_year:
        min_year -= 0.01

    times_plot = figure(
        title=u"Qualifying, Fastest, Average, and Winning Times for " + circuit_name +
              " \u2014 Some data may be missing, zoom for more detail",
        x_axis_label="Year",
        y_axis_label="Lap Time",
        y_range=DataRange1d(start=start, end=end, bounds=(start, end)),
        x_range=Range1d(min_year, max_year, bounds=(min_year, max_year + 3)),
        tools="pan,xbox_zoom,xwheel_zoom,reset,box_zoom,wheel_zoom,save"
    )

    times_plot.yaxis.formatter = DatetimeTickFormatter(**DATETIME_TICK_KWARGS)
    column_source = ColumnDataSource(data=source)
    kwargs = {
        "x": "year",
        "source": column_source,
        "line_width": 2,
        "muted_alpha": 0.05
    }

    avg_lap_time_line = times_plot.line(y="avg_lap_time", line_color="white", **kwargs)
    legend_items = [
        LegendItem(label="Average Race Lap", renderers=[avg_lap_time_line]),
    ]
    tooltips = [
        ("Year", "@year"),
        ("Average Lap Time", "@avg_lap_str"),
    ]

    if source["quali_time"].isna().sum() < source.shape[0]:
        quali_time_line = times_plot.line(y="quali_time", line_color="red", **kwargs)
        legend_items.append(LegendItem(label="Qualifying Fastest", renderers=[quali_time_line]))
        tooltips.append(("Qualifying Lap Time", "@quali_time_str"))

    if source["fastest_lap_time"].isna().sum() < source.shape[0]:
        fastest_lap_time_line = times_plot.line(y="fastest_lap_time", line_color="yellow", **kwargs)
        legend_items.append(LegendItem(label="Fastest Race Lap", renderers=[fastest_lap_time_line]))
        tooltips.append(("Fastest Lap Time", "@fastest_lap_str"))

    # Add rating and other axis
    if source["rating"].replace("", np.nan).isna().sum() < source.shape[0]:
        rating_line = times_plot.line(y="rating_scaled", line_color="green", line_alpha=0.9, name="rating_line",
                                      **kwargs)
        legend_items.append(LegendItem(label="Average Rating", renderers=[rating_line]))
        tooltips.append(("Rating", "@rating"))
        y_range = Range1d(start=0, end=10, bounds=(0, 10))
        times_plot.extra_y_ranges = {"rating_range": y_range}
        axis = LinearAxis(y_range_name="rating_range", axis_label="Rating")
        times_plot.add_layout(axis, "right")

        def update_rating_axis():
            def dt_to_millis(t):
                if isinstance(t, float) or isinstance(t, int):
                    return t
                return t.microsecond / 1000 + t.second * 1000 + t.minute * 1000 * 60
            max_time = dt_to_millis(times_plot.y_range.end)
            min_time = dt_to_millis(times_plot.y_range.start)
            new_rating = (max_time - min_time) * (source["rating"].replace("", np.nan).astype(float) / 10) + min_time
            column_source.patch({
                "rating_scaled": [(slice(new_rating.shape[0]), new_rating)]
            })
            times_plot.extra_y_ranges.update({"rating_range": Range1d(start=0, end=10, bounds=(0, 10))})

        times_plot.y_range.on_change("start", lambda attr, old, new: update_rating_axis())
        times_plot.y_range.on_change("end", lambda attr, old, new: update_rating_axis())

    # Legend
    legend = Legend(items=legend_items, location="top_right", glyph_height=15, spacing=2, inactive_fill_color="gray")
    times_plot.add_layout(legend, "right")
    times_plot.legend.click_policy = "mute"
    times_plot.legend.label_text_font_size = "12pt"  # The default font size

    # Hover tooltip
    times_plot.add_tools(HoverTool(show_arrow=False, tooltips=tooltips))

    # Crosshair
    times_plot.add_tools(CrosshairTool(line_color="white", line_alpha=0.6))

    return times_plot
示例#5
0
def generate_stats_layout(cd_years,
                          cd_races,
                          cd_results,
                          cd_fastest_lap_data,
                          positions_source,
                          circuit_id,
                          driver_id,
                          constructor_id=None):
    """
    Stats div including:
    - Years
    - Num. races
    - Num. wins
    - Num. podiums
    - Best results
    - Average start position
    - Average finish position
    - Average lap time
    - Fastest lap time
    - Num mechanical DNFs and mechanical DNF rate
    - Num crash DNFs and crash DNF rate
    :param cd_years: CD years
    :param cd_races: CD races
    :param cd_results: CD results
    :param cd_fastest_lap_data: CD fastest lap data
    :param positions_source: Positions source
    :param driver_id: Driver ID
    :param circuit_id: Circuit ID
    :param constructor_id: If set to anything but None, will do constructor mode
    :return: Stats div layout
    """
    logging.info("Generating stats div")
    num_races = cd_results.shape[0]
    if num_races == 0:
        return Div()
    win_results = cd_results[cd_results["positionOrder"] == 1]
    num_wins = win_results.shape[0]
    if num_wins > 0:
        rids = win_results["raceId"]
        years = sorted(cd_races.loc[rids.values,
                                    "year"].astype(str).values.tolist(),
                       reverse=True)
        num_wins = str(num_wins) + " (" + ", ".join(years) + ")"
    else:
        num_wins = str(num_wins)
    podium_results = cd_results[cd_results["positionOrder"] <= 3]
    num_podiums = podium_results.shape[0]
    if num_podiums > 0:
        rids = podium_results["raceId"]
        years = list(set(cd_races.loc[rids.values, "year"].values.tolist()))
        years = rounds_to_str(years)
        num_podiums_str = str(num_podiums) + " (" + years + ")"
        if len(num_podiums_str) > 120:
            split = num_podiums_str.split(" ")
            split.insert(int(len(split) / 2), "<br>      " + "".ljust(20))
            num_podiums_str = " ".join(split)
    else:
        num_podiums_str = str(num_podiums)
    best_result = None
    if num_wins == 0:
        idxmin = cd_results["positionOrder"].idxmin()
        if not np.isnan(idxmin):
            rid = cd_results.loc[idxmin, "raceId"]
            year = cd_races.loc[rid, "year"]
            best_result = int_to_ordinal(
                int(cd_results.loc[idxmin, "positionOrder"])) + f" ({year})"
    mean_sp = round(cd_results["grid"].mean(), 1)
    mean_fp = round(cd_results["positionOrder"].mean(), 1)

    avg_lap_time = cd_fastest_lap_data["avg_lap_time_millis"].mean()
    fastest_lap_time = cd_fastest_lap_data["fastest_lap_time_millis"].min()

    classifications = cd_results["statusId"].apply(get_status_classification)
    num_mechanical_dnfs = classifications[classifications ==
                                          "mechanical"].shape[0]
    num_crash_dnfs = classifications[classifications == "crash"].shape[0]
    num_finishes = classifications[classifications == "finished"].shape[0]
    mechanical_dnfs_str = str(num_mechanical_dnfs)
    crash_dnfs_str = str(num_crash_dnfs)
    finishes_str = str(num_finishes)
    if num_races > 0:
        mechanical_dnfs_str += " (" + str(
            round(100 * num_mechanical_dnfs / num_races, 1)) + "%)"
        crash_dnfs_str += " (" + str(round(100 * num_crash_dnfs / num_races,
                                           1)) + "%)"
        finishes_str += " (" + str(round(100 * num_finishes / num_races,
                                         1)) + "%)"

    if positions_source.shape[0] > 0:
        avg_finish_pos_overall = positions_source["avg_finish_pos"].mean()
        avg_finish_pos_here = positions_source["finish_position_int"].mean()
        diff = avg_finish_pos_here - avg_finish_pos_overall
        avg_finish_pos_overall = round(avg_finish_pos_overall, 1)
        avg_finish_pos_here = round(avg_finish_pos_here, 1)
        w = "higher" if diff < 0 else "lower"
        finish_pos_diff_str = f"Finished on average {round(abs(diff), 1)} place(s) {w} than average " \
                              f"(pos. {avg_finish_pos_here} here vs pos. {avg_finish_pos_overall} average overall)"
    else:
        finish_pos_diff_str = ""

    header_template = """
    <h2 style="text-align: center;"><b>{}</b></h2>
    """

    template = """
    <pre><b>{}</b> {}<br></pre>
    """

    if constructor_id:
        name = get_constructor_name(constructor_id, include_flag=False)
    else:
        name = get_driver_name(driver_id, include_flag=False, just_last=True)
    cd_stats = header_template.format(
        f"{name} at {get_circuit_name(circuit_id, include_flag=False)} Stats")
    cd_stats += template.format("Years: ".ljust(22), rounds_to_str(cd_years))
    cd_stats += template.format("Num Races: ".ljust(22), str(num_races))
    cd_stats += template.format("Num Wins: ".ljust(22), str(num_wins))
    cd_stats += template.format("Num Podiums: ".ljust(22),
                                str(num_podiums_str))
    if best_result:
        cd_stats += template.format("Best Result: ".ljust(22),
                                    str(best_result))
    cd_stats += template.format("Avg. Start Pos.: ".ljust(22), mean_sp)
    cd_stats += template.format("Avg. Finish Pos.: ".ljust(22), mean_fp)

    if not np.isnan(avg_lap_time):
        cd_stats += template.format("Avg. Lap Time: ".ljust(22),
                                    millis_to_str(avg_lap_time))
        cd_stats += template.format("Fastest Lap Time: ".ljust(22),
                                    millis_to_str(fastest_lap_time))
    cd_stats += template.format("Num. Mechanical DNFs: ".ljust(22),
                                mechanical_dnfs_str)
    cd_stats += template.format("Num. Crash DNFs: ".ljust(22), crash_dnfs_str)
    cd_stats += template.format("Num Finishes".ljust(22), finishes_str)
    if positions_source.shape[0] > 0:
        cd_stats += template.format("Compared to Average: ".ljust(22),
                                    finish_pos_diff_str)

    return Div(text=cd_stats)
示例#6
0
def generate_results_table(yc_results, yc_fastest_lap_data, year_results, year_fastest_lap_data, year_only=False,
                           height=None, include_driver_name=True, include_constructor_name=False):
    """
    Generates a table of results at each race, including quali position, finish position (or reason for DNF), time, gap
    to leader, fastest lap time and gap to fastest lap (of all drivers), average lap time and gap to fastest average lap
    time (of all drivers).
    :param yc_results: YC results
    :param yc_fastest_lap_data: YC fastest lap data
    :param year_results: Year results
    :param year_fastest_lap_data: Year fastest lap data
    :param year_only: Whether to set the race name row to just the year
    :param height: Plot height
    :param include_driver_name: If True, will include a driver name column
    :param include_constructor_name: If True, will include a constructor name column
    :return: Table layout, source
    """
    # TODO this might be able to be refactored with yeardriver or year, but it is kind of unique
    logging.info("Generating results table")
    source = pd.DataFrame(columns=["race_name", "driver_name", "driver_id ", "race_id", "year", "constructor_name",
                                   "quali_pos_str",
                                   "finish_pos_str",
                                   "time_str",
                                   "fastest_lap_time_str",
                                   "avg_lap_time_str"])
    for idx, results_row in yc_results.sort_values(by=["raceId", "driverId"]).iterrows():
        rid = results_row["raceId"]
        driver_id = results_row["driverId"]
        constructor_id = results_row["constructorId"]
        driver_name = get_driver_name(driver_id)
        constructor_name = get_constructor_name(constructor_id)
        race_results = year_results[year_results["raceId"] == rid]
        race_fastest_lap_data = year_fastest_lap_data[year_fastest_lap_data["raceId"] == rid]
        race_driver_fastest_lap_data = yc_fastest_lap_data[(yc_fastest_lap_data["raceId"] == rid) &
                                                           (yc_fastest_lap_data["driver_id"] == driver_id)]
        race_name = get_race_name(rid)
        grid = results_row["grid"]
        if grid == -1:
            quali_pos_str = "DNQ"
        else:
            quali_pos_str = int_to_ordinal(grid)
        status_id = results_row["statusId"]
        finish_pos_str, finish_pos = result_to_str(results_row["positionOrder"], status_id)
        time = results_row["milliseconds"]
        winner = race_results[race_results["positionOrder"] == 1]
        if winner.shape[0] > 0 and winner["driverId"].values[0] != driver_id \
                and not np.isnan(time) and not np.isnan(results_row["position"]):
            time_gap = millis_to_str(time - winner["milliseconds"].values[0])
            time_str = millis_to_str(time) + " (+" + time_gap + ")"
            if status_id != 1 and get_status_classification(status_id) == "finished":
                time_str = millis_to_str(time) + " (+" + time_gap + ", " + status.loc[status_id, "status"] + ")"
        elif finish_pos == 1:
            time_str = millis_to_str(time)
        else:
            time_str = "Not Set"
        if race_driver_fastest_lap_data.shape[0] > 0:
            fastest_lap_time = race_driver_fastest_lap_data["fastest_lap_time_millis"].values[0]
            fastest_lap_time_str = millis_to_str(fastest_lap_time)
            if race_driver_fastest_lap_data["rank"].values[0] == " 1":
                fastest_lap_time_str = fastest_lap_time_str + " (Fastest)"
            else:
                fastest_time = race_fastest_lap_data[race_fastest_lap_data["rank"] == " 1"]["fastest_lap_time_millis"]
                if fastest_time.shape[0] > 0 and not np.isnan(fastest_lap_time):
                    fastest_time = fastest_time.values[0]
                    fastest_gap = millis_to_str(fastest_lap_time - fastest_time)
                    fastest_lap_time_str = millis_to_str(fastest_lap_time) + " (+" + fastest_gap + ")"
            if fastest_lap_time_str == "":
                fastest_lap_time_str = "Not Set"
            fastest_avg_idx = race_fastest_lap_data["avg_lap_time_millis"].idxmin()
            avg_lap_time = race_driver_fastest_lap_data["avg_lap_time_millis"].values[0]
            if np.isnan(avg_lap_time):
                avg_lap_time_str = "Not Set"
            elif race_fastest_lap_data.loc[fastest_avg_idx, "driver_id"] == driver_id or np.isnan(avg_lap_time):
                avg_lap_time_str = millis_to_str(avg_lap_time) + " (Fastest Avg.)"
            else:
                fastest_avg_time = race_fastest_lap_data.loc[fastest_avg_idx, "avg_lap_time_millis"]
                avg_gap = millis_to_str(avg_lap_time - fastest_avg_time)
                avg_lap_time_str = millis_to_str(avg_lap_time) + " (+" + avg_gap + ")"
        else:
            fastest_lap_time_str = "Not Set"
            avg_lap_time_str = "Not Set"
        source = source.append({
            "race_name": race_name,
            "race_id": rid,
            "driver_name": driver_name,
            "driver_id": driver_id,
            "constructor_name": constructor_name,
            "year": races.loc[rid, "year"],
            "quali_pos_str": quali_pos_str,
            "finish_pos_str": finish_pos_str,
            "time_str": time_str,
            "fastest_lap_time_str": fastest_lap_time_str,
            "avg_lap_time_str": avg_lap_time_str
        }, ignore_index=True)

    source = source.sort_values(by="year", ascending=False)

    results_columns = [
        TableColumn(field="quali_pos_str", title="Grid Pos.", width=75),
        TableColumn(field="finish_pos_str", title="Finish Pos.", width=75),
        TableColumn(field="time_str", title="Time", width=100),
        TableColumn(field="fastest_lap_time_str", title="Fastest Lap Time", width=75),
        TableColumn(field="avg_lap_time_str", title="Avg. Lap Time", width=75),
    ]
    if include_driver_name:
        results_columns.insert(0, TableColumn(field="driver_name", title="Driver Name", width=100))
    if include_constructor_name:
        results_columns.insert(0, TableColumn(field="constructor_name", title="Constructor Name", width=100))
    if year_only:
        results_columns.insert(0, TableColumn(field="year", title="Year", width=50))
    else:
        results_columns.insert(0, TableColumn(field="race_name", title="Race Name", width=100))
    results_table = DataTable(source=ColumnDataSource(data=source), columns=results_columns, index_position=None,
                              height=28 * yc_results.shape[0] if height is None else height)
    title = Div(text=f"<h2><b>Results for each race</b></h2><br><i>The fastest lap time and average lap time gaps "
                     f"shown are calculated based on the gap to the fastest of all drivers and fastest average of "
                     f"all drivers in that race respectively.</i>")
    return column([title, row([results_table], sizing_mode="stretch_width")], sizing_mode="stretch_width"), source
示例#7
0
                       or race_results["rank"].unique().shape[0] == 1
        if not use_lap_data:
            race_data["rank"] = []
        for idx, results_row in race_results.iterrows():
            driver_id = results_row["driverId"]
            race_data["driver_id"].append(driver_id)
            name = get_driver_name(driver_id)
            constructor_id = results_row["constructorId"]
            constructor_name = get_constructor_name(constructor_id)

            driver_laps = race_laps[race_laps["driverId"] == driver_id]
            avg_lap_time_millis = driver_laps["milliseconds"].mean()

            if use_lap_data:
                fastest_lap_millis = driver_laps["milliseconds"].min()
                fastest_lap_str = millis_to_str(fastest_lap_millis)

                race_data["fastest_lap_time_millis"].append(fastest_lap_millis)
                race_data["fastest_lap_time_str"].append(fastest_lap_str)
            else:
                rank = results_row["rank"]
                rank = 0 if np.isnan(rank) or np.isinf(rank) else int(rank)
                fastest_lap_millis = results_row["fastestLapTime"]
                fastest_lap_time_str = millis_to_str(fastest_lap_millis)

                race_data["rank"].append(rank)
                race_data["fastest_lap_time_millis"].append(fastest_lap_millis)
                race_data["fastest_lap_time_str"].append(fastest_lap_time_str)
            if np.isnan(avg_lap_time_millis) and results_row["laps"] > 0:
                if np.isnan(results_row["position"]):
                    avg_lap_time_millis = np.nan