Repository: theOehrly/Fast-F1

Issue:
[BUG] Daniel Ricciardo incorrectly present in 2024 Japanese Grand Prix Practice 1
### Describe the issue:

Daniel Ricciardo didnt take part in the 2024 Japanese Grand Prix Practice 1 session; the driver is shown when fetching the session's results.
https://www.formula1.com/en/results/2024/races/1232/japan/practice/1

![Image](https://github.com/user-attachments/assets/cef2d668-6470-4f83-8447-40fd8b68a435)

### Reproduce the code example:

```python
import fastf1

# Parameters
year = 2024
grand_prix = 4
session = 1

# Load session data
session = fastf1.get_session(year, grand_prix, session)
session.load()

session.results
```

### Error message:

```shell

```


Discussion:
Looks to me like this bug is because Ergast is returning incorrect data. Daniel Riccardo is not in the F1 API drivers list but is in the Ergast drivers list. Correspondingly Iwasa is missing from the Ergast data return.

And then we have this logic for handling when the two sources disagree:

```
else:
    missing_drivers = list(set(driver_info_ergast['DriverNumber'])
                           .difference(driver_info_f1['DriverNumber']))
    # drivers are missing if DNSed (did not start)
    # in that case, pull more information from Ergast for these drivers
```

But Ergast actually do not provide the correct drivers for FP1 since it only has data for the actual grand prix.

```
    def _drivers_results_from_ergast(
            self, *, load_drivers=False, load_results=False
    ) -> Optional[pd.DataFrame]:
        if self.name in self._RACE_LIKE_SESSIONS + self._QUALI_LIKE_SESSIONS:
            session_name = self.name
        else:
            # this is a practice session, use drivers from race session but
            # don't load results
            session_name = 'Race'
            load_results = False
```

So this is actually a much wider issue with reserve drivers taking part in FP1. If you try doing this with most Abu Dhabi GPs' FP1s, your results will contain more than 20 drivers.

We probably need to be a lot more careful about pulling in drivers from Ergast.
This might be fairly straightforward to fix. Right now, we pull driver information for practice sessions from the race endpoint on Ergast, because there is no practice data on Ergast.
Also, to include drivers that did not start (DNS), drivers are added based on the Ergast data to have the full classification result. 
What we probably only need to do is change the data merging for practice sessions such that only the additional values are filled in, but no additional drivers are added. So maybe just skip the "else missing drivers" part above.
I am prepping a PR for this and would prefer to include tests. I tried to find recent examples of drivers DNS but in those cases the drivers are in the F1 API data as well. Do you remember any example that utilizes the pulling additional information from Ergast logic?
I'll check later for an example.
> Do you remember any example that utilizes the pulling additional information from Ergast logic?

Oliver Bearman in 2024 Saudi Arabian GP Practice 1 and 2.

```python
# Parameters
year = 2024
grand_prix = 2
session = 1 # or 2

# Load session data
session = fastf1.get_session(year, grand_prix, session)
session.load()
```

![Image](https://github.com/user-attachments/assets/586bd5f0-924d-49d2-b448-16290f1ae10c)

Best to look for situations where another driver took over driving duties mid-weekend.
This isn't what I was originally looking for but it helped me catch another regression haha. Thanks for flagging it!



Patch:
diff --git a/fastf1/core.py b/fastf1/core.py
index 0f364cc4..5a2e138e 100644
--- a/fastf1/core.py
+++ b/fastf1/core.py
@@ -2179,11 +2179,14 @@ def _check_lap_accuracy(self):
     @soft_exceptions("results", "Failed to load results data!", _logger)
     def _load_drivers_results(self, *, livedata=None):
         # get list of drivers and results
-
         driver_info_f1 = None
+
+        # Ergast returns race, quali and sprint results and driver list
+        # for the corresponding sessions, none for sprint qualifiying/shootout
+        # and the race results and driver list for a practice session.
         driver_info_ergast = None
 
-        info_cols = ('Abbreviation', 'FirstName', 'LastName', 'TeamName',
+        shared_cols = ('Abbreviation', 'FirstName', 'LastName', 'TeamName',
                      'FullName', 'DriverNumber')
 
         # try loading from both sources if they are supported
@@ -2223,23 +2226,32 @@ def _load_drivers_results(self, *, livedata=None):
 
         # F1 and Ergast data
         else:
-            missing_drivers = list(set(driver_info_ergast['DriverNumber'])
-                                   .difference(driver_info_f1['DriverNumber']))
-            # drivers are missing if DNSed (did not start)
-            # in that case, pull more information from Ergast for these drivers
-
             join_cols \
-                = list(set(driver_info_ergast.columns).difference(info_cols))
+                = list(set(driver_info_ergast.columns).difference(shared_cols))
 
+            # do not rely on Ergast driver list for practice and sprint
+            # qualifying sessions
+            ergast_list_accurate = (
+                self.name in self._RACE_LIKE_SESSIONS
+                or self.name == "Qualifying"
+            )
             self._results = SessionResults(
-                driver_info_f1.join(driver_info_ergast.loc[:, join_cols],
-                                    how='outer'),
+                driver_info_f1.join(
+                    driver_info_ergast.loc[:, join_cols],
+                    # Ergast driver list is not accurate for practice and
+                    # sprint qualifying sessions
+                    how='outer' if ergast_list_accurate else "left"
+                ),
                 _force_default_cols=True
             )
 
-            if missing_drivers:
-                self._results.loc[missing_drivers, info_cols] \
-                    = driver_info_ergast.loc[missing_drivers, info_cols]
+            if ergast_list_accurate:
+                # drivers are missing if DNSed (did not start)
+                # fill in their information using Ergast
+                missing_drivers = list(set(driver_info_ergast['DriverNumber'])
+                        .difference(driver_info_f1['DriverNumber']))
+                self._results.loc[missing_drivers, shared_cols] \
+                    = driver_info_ergast.loc[missing_drivers, shared_cols]
 
                 # set (Grid)Position to NaN instead of default last or zero to
                 # make the DNS more obvious


Test patch:
diff --git a/fastf1/tests/test_core.py b/fastf1/tests/test_core.py
index 0ca932a4..34d8116d 100644
--- a/fastf1/tests/test_core.py
+++ b/fastf1/tests/test_core.py
@@ -168,3 +168,25 @@ def test_tyre_data_parsing():
 
     compare = ver[['Stint', 'Compound', 'FreshTyre', 'TyreLife']]
     assert compare.equals(ref)
+
+
+def test_session_results_drivers():
+    # Sainz (55) is replaced by Bearman (38) after FP2
+    session = fastf1.get_session(2024, "Saudi Arabia", 2)
+    session.load(laps=False, telemetry=False, weather=False)
+    drivers = session.results.index
+    assert "55" in drivers
+    assert "38" not in drivers
+
+    session = fastf1.get_session(2024, "Saudi Arabia", 3)
+    session.load(laps=False, telemetry=False, weather=False)
+    drivers = session.results.index
+    assert "38" in drivers
+    assert "55" not in drivers
+
+    # Mick Schumacher (47) is not officially classified
+    # and does not appear in the F1 API response
+    session = fastf1.get_session(2022, "Saudi Arabia", "R")
+    session.load(laps=False, telemetry=False, weather=False)
+    drivers = session.results.index
+    assert "47" in drivers