Skip to content

Commit

Permalink
MNT: compatibility with pandas>=2.1.0 and upcoming deprecations
Browse files Browse the repository at this point in the history
  • Loading branch information
theOehrly committed Dec 28, 2023
1 parent 1987fcb commit bf5e1dc
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 163 deletions.
104 changes: 16 additions & 88 deletions fastf1/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,15 @@
from functools import cached_property
import warnings
import typing
from typing import Optional, List, Literal, Iterable, Union, Tuple, Any
from typing import Optional, List, Literal, Iterable, Union, Tuple, Any, Type

import numpy as np
import pandas as pd

import fastf1
from fastf1 import _api as api
from fastf1 import ergast
from fastf1.internals.pandas_base import BaseDataFrame, BaseSeries
from fastf1.livetiming.data import LiveTimingData
from fastf1.mvapi import get_circuit_info, CircuitInfo
from fastf1.logger import get_logger, soft_exceptions
Expand Down Expand Up @@ -212,10 +213,7 @@ def __init__(self,

@property
def _constructor(self):
def _new(*args, **kwargs):
return Telemetry(*args, **kwargs).__finalize__(self)

return _new
return Telemetry

@property
def base_class_view(self):
Expand Down Expand Up @@ -1689,7 +1687,8 @@ def _fix_missing_laps_retired_on_track(self):
})

# add generated laps at the end and fix sorting at the end
self._laps = pd.concat([self._laps, new_last])
self._laps = (pd.concat([self._laps, new_last])
.__finalize__(self._laps))
any_new = True

if any_new:
Expand Down Expand Up @@ -2394,7 +2393,7 @@ def _calculate_t0_date(self, *tel_data_sets: dict):
self._t0_date = date_offset.round('ms')


class Laps(pd.DataFrame):
class Laps(BaseDataFrame):
"""Object for accessing lap (timing) data of multiple laps.
Args:
Expand Down Expand Up @@ -2548,8 +2547,7 @@ class Laps(pd.DataFrame):
}

_metadata = ['session']
_internal_names = pd.DataFrame._internal_names \
+ ['base_class_view', 'telemetry']
_internal_names = BaseDataFrame._internal_names + ['telemetry']
_internal_names_set = set(_internal_names)

QUICKLAP_THRESHOLD = 1.07
Expand Down Expand Up @@ -2588,30 +2586,8 @@ def __init__(self,
self.session = session

@property
def _constructor(self):
def _new(*args, **kwargs):
return Laps(*args, **kwargs).__finalize__(self)

return _new

@property
def _constructor_sliced(self):
def _new(*args, **kwargs):
name = kwargs.get('name')
if name and (name in self.columns):
# vertical slice
return pd.Series(*args, **kwargs).__finalize__(self)

# horizontal slice
return Lap(*args, **kwargs).__finalize__(self)

return _new

@property
def base_class_view(self):
"""For a nicer debugging experience; can now view as
dataframe in various IDEs"""
return pd.DataFrame(self)
def _constructor_sliced_horizontal(self) -> Type["Lap"]:
return Lap

@cached_property
def telemetry(self) -> Telemetry:
Expand Down Expand Up @@ -3247,7 +3223,7 @@ def iterlaps(self, require: Optional[Iterable] = None) \
yield index, lap


class Lap(pd.Series):
class Lap(BaseSeries):
"""
Object for accessing lap (timing) data of a single lap.
Expand All @@ -3256,19 +3232,9 @@ class Lap(pd.Series):
telemetry data.
"""
_metadata = ['session']
_internal_names = pd.Series._internal_names + ['telemetry']
_internal_names = BaseSeries._internal_names + ['telemetry']
_internal_names_set = set(_internal_names)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

@property
def _constructor(self):
def _new(*args, **kwargs):
return Lap(*args, **kwargs).__finalize__(self)

return _new

@cached_property
def telemetry(self) -> Telemetry:
"""Telemetry data for this lap
Expand Down Expand Up @@ -3429,7 +3395,7 @@ def get_weather_data(self) -> pd.Series:
return pd.Series(index=self.session.weather_data.columns)


class SessionResults(pd.DataFrame):
class SessionResults(BaseDataFrame):
"""This class provides driver and result information for all drivers that
participated in a session.
Expand Down Expand Up @@ -3565,9 +3531,6 @@ class SessionResults(pd.DataFrame):
'Points': 'float64'
}

_internal_names = pd.DataFrame._internal_names + ['base_class_view']
_internal_names_set = set(_internal_names)

def __init__(self, *args, force_default_cols: bool = False, **kwargs):
if force_default_cols:
kwargs['columns'] = list(self._COL_TYPES.keys())
Expand All @@ -3586,37 +3549,12 @@ def __init__(self, *args, force_default_cols: bool = False, **kwargs):

self[col] = self[col].astype(_type)

def __repr__(self):
return self.base_class_view.__repr__()

@property
def _constructor(self):
def _new(*args, **kwargs):
return SessionResults(*args, **kwargs).__finalize__(self)

return _new

@property
def _constructor_sliced(self):
def _new(*args, **kwargs):
name = kwargs.get('name')
if name and (name in self.columns):
# vertical slice
return pd.Series(*args, **kwargs).__finalize__(self)

# horizontal slice
return DriverResult(*args, **kwargs).__finalize__(self)
def _constructor_sliced_horizontal(self) -> Type["DriverResult"]:
return DriverResult

return _new

@property
def base_class_view(self):
"""For a nicer debugging experience; can view DataFrame through
this property in various IDEs"""
return pd.DataFrame(self)


class DriverResult(pd.Series):
class DriverResult(BaseSeries):
"""This class provides driver and result information for a single driver.
This class subclasses a :class:`pandas.Series` and the usual methods
Expand All @@ -3635,19 +3573,9 @@ class DriverResult(pd.Series):
.. versionadded:: 2.2
"""

_internal_names = pd.DataFrame._internal_names + ['dnf']
_internal_names = BaseSeries._internal_names + ['dnf']
_internal_names_set = set(_internal_names)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

@property
def _constructor(self):
def _new(*args, **kwargs):
return DriverResult(*args, **kwargs).__finalize__(self)

return _new

@property
def dnf(self) -> bool:
"""True if driver did not finish"""
Expand Down
56 changes: 15 additions & 41 deletions fastf1/ergast/interface.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import copy
import json
from typing import List, Literal, Optional, Union
from typing import List, Literal, Optional, Type, Union

from fastf1 import __version_short__
from fastf1.req import Cache
import fastf1.ergast.structure as API


import pandas as pd
from fastf1.internals.pandas_base import BaseDataFrame, BaseSeries


BASE_URL = 'https://ergast.com/api/f1'
Expand Down Expand Up @@ -101,7 +99,7 @@ def get_prev_result_page(self) -> Union['ErgastSimpleResponse',
)


class ErgastResultFrame(pd.DataFrame):
class ErgastResultFrame(BaseDataFrame):
"""
Wraps a Pandas ``DataFrame``. Additionally, this class can be
initialized from Ergast response data with automatic flattening and type
Expand All @@ -117,7 +115,7 @@ class ErgastResultFrame(pd.DataFrame):
auto_cast: Determines if values are automatically cast to the most
appropriate data type from their original string representation
"""
_internal_names = pd.DataFrame._internal_names + ['base_class_view']
_internal_names = BaseDataFrame._internal_names + ['base_class_view']
_internal_names_set = set(_internal_names)

def __init__(self, data=None, *,
Expand Down Expand Up @@ -164,48 +162,17 @@ def _flatten_element(cls, nested: dict, category: dict, cast: bool):
return nested, flat

@property
def _constructor(self):
def _new(*args, **kwargs):
return ErgastResultFrame(*args, **kwargs).__finalize__(self)

return _new

@property
def _constructor_sliced(self):
def _new(*args, **kwargs):
name = kwargs.get('name')
if name and (name in self.columns):
# vertical slice
return pd.Series(*args, **kwargs).__finalize__(self)

# horizontal slice
return ErgastResultSeries(*args, **kwargs).__finalize__(self)

return _new

@property
def base_class_view(self):
"""For a nicer debugging experience; can view DataFrame through
this property in various IDEs"""
return pd.DataFrame(self)
def _constructor_sliced_horizontal(self):
return ErgastResultSeries


class ErgastResultSeries(pd.Series):
class ErgastResultSeries(BaseSeries):
"""
Wraps a Pandas ``Series``.
Currently, no extra functionality is implemented.
"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

@property
def _constructor(self):
def _new(*args, **kwargs):
return ErgastResultSeries(*args, **kwargs).__finalize__(self)

return _new
pass


class ErgastRawResponse(ErgastResponseMixin, list):
Expand Down Expand Up @@ -275,6 +242,13 @@ class ErgastSimpleResponse(ErgastResponseMixin, ErgastResultFrame):
+ ErgastResponseMixin._internal_names
_internal_names_set = set(_internal_names)

@property
def _constructor(self) -> Type["ErgastResultFrame"]:
# drop from ErgastSimpleResponse to ErgastResultFrame, removing the
# ErgastResponseMixin because a slice of the data is no longer a full
# response and pagination, ... is therefore not supported anymore
return ErgastResultFrame


class ErgastMultiResponse(ErgastResponseMixin):
"""
Expand Down
40 changes: 6 additions & 34 deletions fastf1/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@
import datetime
import json
import warnings
from typing import Literal, Union, Optional
from typing import Callable, Literal, Union, Optional

import dateutil.parser

Expand All @@ -180,6 +180,7 @@
import fastf1._api
from fastf1.core import Session
import fastf1.ergast
from fastf1.internals.pandas_base import BaseSeries, BaseDataFrame
from fastf1.logger import get_logger, soft_exceptions
from fastf1.req import Cache
from fastf1.utils import recursive_dict_get, to_datetime, to_timedelta
Expand Down Expand Up @@ -735,7 +736,7 @@ def _get_schedule_from_ergast(year) -> "EventSchedule":
return schedule


class EventSchedule(pd.DataFrame):
class EventSchedule(BaseDataFrame):
"""This class implements a per-season event schedule.
For detailed information about the information that is available for each
Expand Down Expand Up @@ -784,9 +785,6 @@ class EventSchedule(pd.DataFrame):

_metadata = ['year']

_internal_names = pd.DataFrame._internal_names + ['base_class_view']
_internal_names_set = set(_internal_names)

def __init__(self, *args, year: int = 0,
force_default_cols: bool = False, **kwargs):
if force_default_cols:
Expand All @@ -807,28 +805,9 @@ def __init__(self, *args, year: int = 0,
self[col] = _type()
self[col] = self[col].astype(_type)

def __repr__(self):
return self.base_class_view.__repr__()

@property
def _constructor(self):
def _new(*args, **kwargs):
return EventSchedule(*args, **kwargs).__finalize__(self)

return _new

@property
def _constructor_sliced(self):
def _new(*args, **kwargs):
return Event(*args, **kwargs).__finalize__(self)

return _new

@property
def base_class_view(self):
"""For a nicer debugging experience; can view DataFrame through
this property in various IDEs"""
return pd.DataFrame(self)
def _constructor_sliced_horizontal(self) -> Callable[..., "Event"]:
return Event

def is_testing(self):
"""Return `True` or `False`, depending on whether each event is a
Expand Down Expand Up @@ -934,7 +913,7 @@ def get_event_by_name(
return self._fuzzy_event_search(name)


class Event(pd.Series):
class Event(BaseSeries):
"""This class represents a single event (race weekend or testing event).
Each event consists of one or multiple sessions, depending on the type
Expand All @@ -955,13 +934,6 @@ def __init__(self, *args, year: int = None, **kwargs):
super().__init__(*args, **kwargs)
self.year = year

@property
def _constructor(self):
def _new(*args, **kwargs):
return Event(*args, **kwargs).__finalize__(self)

return _new

def is_testing(self) -> bool:
"""Return `True` or `False`, depending on whether this event is a
testing event."""
Expand Down
Loading

0 comments on commit bf5e1dc

Please sign in to comment.