Esempio n. 1
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")
Esempio n. 2
0
def get_layout(year_id=-1,
               circuit_id=-1,
               driver_id=-1,
               download_image=True,
               **kwargs):
    # Generate slices
    year_races = races[races["year"] == year_id]
    race = year_races[year_races["circuitId"] == circuit_id]
    if race.shape[0] == 0:
        return generate_error_layout(year_id=year_id,
                                     circuit_id=circuit_id,
                                     driver_id=driver_id)
    rid = race.index.values[0]
    race_results = results[results["raceId"] == rid]
    ycd_results = race_results[race_results["driverId"] == driver_id]

    logging.info(
        f"Generating layout for mode YEARCIRCUITDRIVER in yearcircuitdriver, year_id={year_id}, "
        f"circuit_id={circuit_id}, driver_id={driver_id}")

    if ycd_results.shape[0] == 0:
        return generate_error_layout(year_id=year_id,
                                     circuit_id=circuit_id,
                                     driver_id=driver_id)

    # Generate more slices
    race_laps = lap_times[lap_times["raceId"] == rid]
    ycd_laps = race_laps[race_laps["driverId"] == driver_id]
    ycd_pit_stop_data = pit_stop_data[(pit_stop_data["raceId"] == rid) &
                                      (pit_stop_data["driverId"] == driver_id)]
    race_quali = quali[quali["raceId"] == rid]
    ycd_fastest_lap_data = fastest_lap_data[
        (fastest_lap_data["raceId"] == rid)
        & (fastest_lap_data["driver_id"] == driver_id)]
    year_driver_standings = driver_standings[driver_standings["raceId"].isin(
        year_races.index.values)]

    disclaimer_sc, sc_starts, sc_ends = detect_safety_car(race_laps, race)
    disclaimer_sc = PlotItem(disclaimer_sc, [], "", listed=False)
    disclaimer_overtakes, overtake_data = detect_overtakes(ycd_laps, race_laps)
    disclaimer_overtakes = PlotItem(disclaimer_overtakes, [], "", listed=False)

    gap_plot, cached_driver_map = generate_gap_plot(
        race_laps,
        race_results,
        driver_id,
        sc_starts=sc_starts,
        sc_ends=sc_ends,
        overtake_data=overtake_data,
        ycd_pit_stop_data=ycd_pit_stop_data)
    gap_plot = PlotItem(gap_plot, [],
                        COMMON_PLOT_DESCRIPTIONS["generate_gap_plot"])

    position_plot = PlotItem(
        generate_position_plot,
        [race_laps, race_results, cached_driver_map, driver_id],
        COMMON_PLOT_DESCRIPTIONS["generate_position_plot"],
        kwargs=dict(sc_starts=sc_starts,
                    sc_ends=sc_ends,
                    ycd_pit_stop_data=ycd_pit_stop_data))

    lap_time_plot = PlotItem(
        generate_lap_time_plot,
        [race_laps, race_results, cached_driver_map, driver_id],
        COMMON_PLOT_DESCRIPTIONS["generate_times_plot"],
        kwargs=dict(sc_starts=sc_starts,
                    sc_ends=sc_ends,
                    overtake_data=overtake_data))

    description = u"Qualifying Table \u2014 table containing information about qualifying times and rank"
    quali_table, quali_source = generate_quali_table(race_quali, race_results,
                                                     driver_id)
    quali_table = PlotItem(quali_table, [], description)

    description = u"Various statistics on this driver at this race"
    stats_layout = PlotItem(generate_stats_layout, [
        ycd_results, ycd_pit_stop_data, ycd_fastest_lap_data,
        year_driver_standings, race_results, quali_source, rid, circuit_id,
        driver_id
    ],
                            description,
                            kwargs=dict(download_image=download_image))

    driver_name = get_driver_name(driver_id)
    race_name = get_race_name(rid, include_year=True)
    header = generate_div_item(
        f"<h2><b>What did {driver_name}'s {race_name} look like?"
        f"</b></h2><br><i>Yellow dashed vertical lines show the start of a safety car period, "
        f"orange vertical lines show the end*. "
        f"<br>The white line marks the fastest lap of the race."
        f"<br>Green lines show overtakes and red lines show being overtaken**."
        f"<br>Pink lines show pit stops along with how long was spent in the pits.</i>"
    )

    middle_spacer = generate_spacer_item()
    layout = generate_plot_list_selector([[header], [gap_plot],
                                          [middle_spacer], [position_plot],
                                          [middle_spacer], [lap_time_plot],
                                          [middle_spacer], [disclaimer_sc],
                                          [disclaimer_overtakes],
                                          [quali_table], [stats_layout]])

    logging.info("Finished generating layout for mode YEARCIRCUITDRIVER")

    return layout
Esempio n. 3
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")
def get_layout(year_id=-1,
               circuit_id=-1,
               constructor_id=-1,
               download_image=True,
               **kwargs):
    # Generate slices
    year_races = races[races["year"] == year_id]
    race = year_races[year_races["circuitId"] == circuit_id]
    if race.shape[0] == 0:
        return generate_error_layout(year_id=year_id,
                                     circuit_id=circuit_id,
                                     constructor_id=constructor_id)
    rid = race.index.values[0]
    race_results = results[results["raceId"] == rid]
    ycc_results = race_results[race_results["constructorId"] == constructor_id]

    logging.info(
        f"Generating layout for mode YEARCIRCUITCONSTRUCTOR in yearcircuitconstructor, year_id={year_id}, "
        f"circuit_id={circuit_id}, constructor_id={constructor_id}")

    if ycc_results.shape[0] == 0:
        return generate_error_layout(year_id=year_id,
                                     circuit_id=circuit_id,
                                     constructor_id=constructor_id)

    # Generate more slices
    driver_ids = ycc_results["driverId"].unique()
    ycc_pit_stop_data = pit_stop_data[(pit_stop_data["raceId"] == rid) & (
        pit_stop_data["driverId"].isin(driver_ids))]
    race_laps = lap_times[lap_times["raceId"] == rid]
    race_quali = quali[quali["raceId"] == rid]
    ycc_fastest_lap_data = fastest_lap_data[
        (fastest_lap_data["raceId"] == rid)
        & (fastest_lap_data["driver_id"].isin(driver_ids))]
    year_driver_standings = driver_standings[driver_standings["raceId"].isin(
        year_races.index.values)]
    year_constructor_standings = constructor_standings[
        constructor_standings["raceId"].isin(year_races.index.values)]

    disclaimer_sc, sc_starts, sc_ends = detect_safety_car(race_laps, race)
    disclaimer_sc = PlotItem(disclaimer_sc, [], "", listed=False)

    gap_plot, cached_driver_map = generate_gap_plot(
        race_laps,
        race_results,
        driver_ids,
        constructor_id,
        sc_starts=sc_starts,
        sc_ends=sc_ends,
        ycc_pit_stop_data=ycc_pit_stop_data)
    gap_plot = PlotItem(gap_plot, [],
                        COMMON_PLOT_DESCRIPTIONS["generate_gap_plot"])

    position_plot = PlotItem(
        generate_position_plot, [
            race_laps, race_results, cached_driver_map, driver_ids,
            constructor_id
        ],
        COMMON_PLOT_DESCRIPTIONS["generate_position_plot"],
        kwargs=dict(sc_starts=sc_starts,
                    sc_ends=sc_ends,
                    ycc_pit_stop_data=ycc_pit_stop_data))

    lap_time_plot = PlotItem(generate_lap_time_plot, [
        race_laps, race_results, cached_driver_map, driver_ids, constructor_id
    ],
                             COMMON_PLOT_DESCRIPTIONS["generate_times_plot"],
                             kwargs=dict(sc_starts=sc_starts, sc_ends=sc_ends))

    quali_table, quali_source = generate_quali_table(race_quali, race_results,
                                                     driver_ids)
    quali_table = PlotItem(quali_table, [], "add descr. " * 5)

    stats_layout = PlotItem(generate_stats_layout, [
        ycc_results, ycc_pit_stop_data, ycc_fastest_lap_data,
        year_driver_standings, year_constructor_standings, quali_source, rid,
        circuit_id, constructor_id, driver_ids
    ],
                            "add descr. " * 5,
                            kwargs=dict(download_image=download_image))

    constructor_name = get_constructor_name(constructor_id)
    race_name = get_race_name(rid, include_year=True)
    header = generate_div_item(
        f"<h2><b>What did {constructor_name}'s {race_name} look like?"
        f"</b></h2><br><i>Yellow dashed vertical lines show the start of a safety car period, "
        f"orange vertical lines show the end.*"
        f"<br>The white line marks the fastest lap of the race."
        f"<br>Pink lines show pit stops along with how long was spent in the pits."
    )

    middle_spacer = generate_spacer_item()
    group = generate_plot_list_selector([[header], [gap_plot], [middle_spacer],
                                         [position_plot], [middle_spacer],
                                         [lap_time_plot], [middle_spacer],
                                         [disclaimer_sc], [quali_table],
                                         [stats_layout]])

    logging.info("Finished generating layout for mode YEARCIRCUITCONSTRUCTOR")

    return group
Esempio n. 5
0
def generate_stats_layout(positions_source, yc_results, comparison_source, year_id, constructor_id):
    """
    Year summary div, including WCC place, highest race finish, number of races, points, points per race, number of
    wins, number of podiums, and everything else in constructor.generate_stats_layout and
    yeardriver.generate_stats_layout
    - WCC place
    - Highest race finish
    - Number of races
    - Points
    - Points per race
    - Number of wins and where were they
    - Number of podiums and where were they
    - Teammates
    - Constructors
    - Mean gap to teammate in positions
    - Mean grid position
    - Mean finish position
    - DNF info
    :param positions_source: Positions source
    :param yc_results: YC results
    :param comparison_source: Comparison source
    :param year_id: Year ID
    :param constructor_id: Constructor ID
    :return: Stats layout
    """
    logging.info("Generating year constructor stats layout")
    if positions_source.shape[0] == 0:
        return Div(text="")
    wcc_final_standing = positions_source["wcc_final_standing"].mode()
    if wcc_final_standing.shape[0] > 0:
        wcc_final_standing_str = int_to_ordinal(wcc_final_standing.values[0])
    else:
        wcc_final_standing_str = ""
    highest_race_finish_idx = yc_results["positionOrder"].idxmin()
    if np.isnan(highest_race_finish_idx):
        highest_race_finish_str = ""
    else:
        highest_race_finish = yc_results.loc[highest_race_finish_idx, "positionOrder"]
        round_name = get_race_name(yc_results.loc[highest_race_finish_idx, "raceId"])
        highest_race_finish_str = int_to_ordinal(highest_race_finish) + " at " + round_name
    num_races = positions_source["race_id"].unique().shape[0]
    num_races_str = str(num_races)
    points = positions_source["points"].max()
    if np.isnan(points):
        points_str = ""
    elif points <= 0:
        points_str = str(points) + " (0 pts/race)"
    else:
        points_str = str(points) + " (" + str(round(points / num_races, 1)) + " pts/race)"
    wins_slice = yc_results[yc_results["positionOrder"] == 1]
    num_wins = wins_slice.shape[0]
    if num_wins == 0:
        wins_str = str(num_wins)
    else:
        wins_str = str(num_wins) + " (" + ", ".join(wins_slice["raceId"].apply(get_race_name)) + ")"
        if len(wins_str) > 120:
            split = wins_str.split(" ")
            split.insert(int(len(split) / 2), "<br>    " + "".ljust(20))
            wins_str = " ".join(split)
    podiums_slice = yc_results[yc_results["positionOrder"] <= 3]
    num_podiums = podiums_slice.shape[0]
    if num_podiums == 0:
        podiums_str = str(num_podiums)
    else:
        race_names = ", ".join([get_race_name(rid) for rid in podiums_slice["raceId"].unique()])
        podiums_str = str(num_podiums) + " (" + race_names + ")"
        if len(podiums_str) > 120:
            split = podiums_str.split(" ")
            split.insert(int(len(split) / 2), "<br>    " + "".ljust(20))
            podiums_str = " ".join(split)
    driver_dids = yc_results["driverId"].unique()
    driver_names = []
    for did in driver_dids:
        driver_names.append(get_driver_name(did))
    driver_names = ", ".join(driver_names)
    mean_grid_pos = yc_results["grid"].replace("", np.nan).mean()
    if np.isnan(mean_grid_pos):
        mean_grid_pos_str = ""
    else:
        mean_grid_pos_str = str(round(mean_grid_pos, 1))
    mean_finish_pos = yc_results["positionOrder"].mean()
    if np.isnan(mean_finish_pos):
        mean_finish_pos_str = ""
    else:
        mean_finish_pos_str = str(round(mean_finish_pos, 1))
    classifications = yc_results["statusId"].apply(get_status_classification)
    num_mechanical_dnfs = classifications[classifications == "mechanical"].shape[0]
    num_crash_dnfs = classifications[classifications == "crash"].shape[0]
    if num_races > 0:
        num_mechanical_dnfs_str = str(num_mechanical_dnfs) + " (" + \
                                  str(round(100 * num_mechanical_dnfs / num_races, 1)) + "%)"
        num_crash_dnfs_str = str(num_crash_dnfs) + " (" + str(round(100 * num_crash_dnfs / num_races, 1)) + "%)"
    else:
        num_mechanical_dnfs_str = ""
        num_crash_dnfs_str = ""
    mean_teammate_gap_pos = (comparison_source["driver1_fp"] - comparison_source["driver2_fp"]).mean()
    if np.isnan(mean_teammate_gap_pos):
        mean_teammate_gap_pos_str = ""
    else:
        mean_teammate_gap_pos_str = "Driver {} finished {} places better than driver {} on average"
        mean_teammate_gap_pos_str = mean_teammate_gap_pos_str.format("1" if mean_teammate_gap_pos < 0 else "2",
                                                                     str(abs(round(mean_teammate_gap_pos, 1))),
                                                                     "2" if mean_teammate_gap_pos < 0 else "1")

    # Construct the HTML
    header_template = """
    <h2 style="text-align: left;"><b>{}</b></h2>
    """
    template = """
    <pre><b>{}</b> {}<br></pre>
    """

    constructor_name = get_constructor_name(constructor_id, include_flag=False)
    constructor_stats = header_template.format(f"{constructor_name}'s Stats for the {year_id} Season")
    constructor_stats += template.format("WCC Final Pos.: ".ljust(20), wcc_final_standing_str)
    constructor_stats += template.format("Num. Races: ".ljust(20), num_races_str)
    if num_wins == 0:
        constructor_stats += template.format("Best Finish Pos.: ".ljust(20), highest_race_finish_str)
    constructor_stats += template.format("Wins: ".ljust(20), wins_str)
    constructor_stats += template.format("Podiums: ".ljust(20), podiums_str)
    constructor_stats += template.format("Points: ".ljust(20), points_str)
    constructor_stats += template.format("Drivers(s): ".ljust(20), driver_names)
    constructor_stats += template.format("Avg. Grid Pos.: ".ljust(20), mean_grid_pos_str)
    constructor_stats += template.format("Avg. Finish Pos.: ".ljust(20), mean_finish_pos_str)
    constructor_stats += template.format("Mechanical DNFs: ".ljust(20), num_mechanical_dnfs_str)
    constructor_stats += template.format("Crash DNFs: ".ljust(20), num_crash_dnfs_str)
    constructor_stats += template.format("Avg. Driver Gap: ".ljust(20), mean_teammate_gap_pos_str)

    return Div(text=constructor_stats)
Esempio n. 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