[ad_1]
We’re prepared now to play with the information to create the visualisations.
Challenges:
To acquire the information wanted for the visuals my first instinct was: take a look at the cumulative distance column for each runner, establish when a lap distance was accomplished (1000, 2000, 3000, and many others.) by every of them and do the variations of timestamps.
That algorithm seems easy, and may work, nevertheless it had some limitations that I wanted to deal with:
Precise lap distances are sometimes accomplished in between two knowledge factors registered. To be extra correct I needed to do interpolation of each place and time.As a consequence of distinction within the precision of gadgets, there is likely to be misalignments throughout runners. The most common is when a runner’s lap notification beeps earlier than one other one even when they’ve been collectively the entire monitor. To minimise this I made a decision to make use of the reference runner to set the place marks for each lap within the monitor. The time distinction might be calculated when different runners cross these marks (despite the fact that their cumulative distance is forward or behind the lap). That is extra near the truth of the race: if somebody crosses some extent earlier than, they’re forward (regardless the cumulative distance of their gadget)With the earlier level comes one other drawback: the latitude and longitude of a reference mark may by no means be precisely registered on the opposite runners’ knowledge. I used Nearest Neighbours to seek out the closest datapoint by way of place.Lastly, Nearest Neighbours may carry incorrect datapoints if the monitor crosses the identical positions at totally different moments in time. So the inhabitants the place the Nearest Neighbours will search for the most effective match must be lowered to a smaller group of candidates. I outlined a window measurement of 20 datapoints across the goal distance (distance_cum).
Algorithm
With all of the earlier limitations in thoughts, the algorithm needs to be as follows:
1. Select the reference and a lap distance (default= 1km)
2. Utilizing the reference knowledge, establish the place and the second each lap was accomplished: the reference marks.
3. Go to different runner’s knowledge and establish the moments they crossed these place marks. Then calculate the distinction in time of each runners crossing the marks. Lastly the delta of this time distinction to characterize the evolution of the hole.
Code Instance
1. Select the reference and a lap distance (default= 1km)
Juan would be the reference (juan_df) on the examples.The opposite runners might be Pedro (pedro_df ) and Jimena (jimena_df).Lap distance might be 1000 metres
2. Create interpolate_laps(): operate that finds or interpolates the precise level for every accomplished lap and return it in a brand new dataframe. The inferpolation is finished with the operate: interpolate_value() that was additionally created.
## Perform: interpolate_value()
Enter: – begin: The beginning worth.- finish: The ending worth.- fraction: A worth between 0 and 1 that represents the place between the beginning and finish values the place the interpolation ought to happen.Return:- The interpolated worth that lies between the beginning and finish values on the specified fraction.
def interpolate_value(begin, finish, fraction):return begin + (finish – begin) * fraction## Perform: interpolate_laps()
Enter: – track_df: dataframe with monitor knowledge.- lap_distance: metres per lap (default 1000)Return:- track_laps: dataframe with lap metrics. As many rows as laps recognized.
def interpolate_laps(track_df , lap_distance = 1000):#### 1. Initialise track_laps with the primary row of track_df track_laps = track_df.loc[0][[‘latitude’,’longitude’,’elevation’,’date_time’,’distance_cum’]].copy()
# Set distance_cum = 0track_laps[[‘distance_cum’]] = 0
# Transpose dataframetrack_laps = pd.DataFrame(track_laps)track_laps = track_laps.transpose()
#### 2. Calculate number_of_laps = Whole Distance / lap_distancenumber_of_laps = track_df[‘distance_cum’].max()//lap_distance
#### 3. For every lap i from 1 to number_of_laps:for i in vary(1,int(number_of_laps+1),1):
# a. Calculate target_distance = i * lap_distancetarget_distance = i*lap_distance
# b. Discover first_crossing_index the place track_df[‘distance_cum’] > target_distancefirst_crossing_index = (track_df[‘distance_cum’] > target_distance).idxmax()
# c. If match is precisely the lap distance, copy that rowif (track_df.loc[first_crossing_index][‘distance_cum’] == target_distance):new_row = track_df.loc[first_crossing_index][[‘latitude’,’longitude’,’elevation’,’date_time’,’distance_cum’]]
# Else: Create new_row with interpolated values, copy that row.else:
fraction = (target_distance – track_df.loc[first_crossing_index-1, ‘distance_cum’]) / (track_df.loc[first_crossing_index, ‘distance_cum’] – track_df.loc[first_crossing_index-1, ‘distance_cum’])
# Create the brand new rownew_row = pd.Collection({‘latitude’: interpolate_value(track_df.loc[first_crossing_index-1, ‘latitude’], track_df.loc[first_crossing_index, ‘latitude’], fraction),’longitude’: interpolate_value(track_df.loc[first_crossing_index-1, ‘longitude’], track_df.loc[first_crossing_index, ‘longitude’], fraction),’elevation’: interpolate_value(track_df.loc[first_crossing_index-1, ‘elevation’], track_df.loc[first_crossing_index, ‘elevation’], fraction),’date_time’: track_df.loc[first_crossing_index-1, ‘date_time’] + (track_df.loc[first_crossing_index, ‘date_time’] – track_df.loc[first_crossing_index-1, ‘date_time’]) * fraction,’distance_cum’: target_distance}, title=f’lap_{i}’)
# d. Add the brand new row to the dataframe that shops the lapsnew_row_df = pd.DataFrame(new_row)new_row_df = new_row_df.transpose()
track_laps = pd.concat([track_laps,new_row_df])
#### 4. Convert date_time to datetime format and take away timezonetrack_laps[‘date_time’] = pd.to_datetime(track_laps[‘date_time’], format=’%Y-%m-%d %H:%M:%S.%fpercentz’)track_laps[‘date_time’] = track_laps[‘date_time’].dt.tz_localize(None)
#### 5. Calculate seconds_diff between consecutive rows in track_lapstrack_laps[‘seconds_diff’] = track_laps[‘date_time’].diff()
return track_laps
Making use of the interpolate operate to the reference dataframe will generate the next dataframe:
juan_laps = interpolate_laps(juan_df , lap_distance=1000)
Notice because it was a 10k race, 10 laps of 1000m has been recognized (see column distance_cum). The column seconds_diff has the time per lap. The remainder of the columns (latitude, longitude, elevation and date_time) mark the place and time for every lap of the reference as the results of interpolation.
3. To calculate the time gaps between the reference and the opposite runners I created the operate gap_to_reference()
## Helper Features:- get_seconds(): Convert timedelta to whole seconds- format_timedelta(): Format timedelta as a string (e.g., “+01:23” or “-00:45”)# Convert timedelta to whole secondsdef get_seconds(td):# Convert to whole secondstotal_seconds = td.total_seconds()
return total_seconds
# Format timedelta as a string (e.g., “+01:23” or “-00:45”)def format_timedelta(td):# Convert to whole secondstotal_seconds = td.total_seconds()
# Decide signsign = ‘+’ if total_seconds >= 0 else ‘-‘
# Take absolute worth for calculationtotal_seconds = abs(total_seconds)
# Calculate minutes and remaining secondsminutes = int(total_seconds // 60)seconds = int(total_seconds % 60)
# Format the stringreturn f”{signal}{minutes:02d}:{seconds:02d}”
## Perform: gap_to_reference()
Enter: – laps_dict: dictionary containing the df_laps for all of the runnners’ names- df_dict: dictionary containing the track_df for all of the runnners’ names- reference_name: title of the referenceReturn:- matches: processed knowledge with time variations.
def gap_to_reference(laps_dict, df_dict, reference_name):#### 1. Get the reference’s lap knowledge from laps_dictmatches = laps_dict[reference_name][[‘latitude’,’longitude’,’date_time’,’distance_cum’]]
#### 2. For every racer (title) and their knowledge (df) in df_dict:for title, df in df_dict.objects():
# If racer is the reference: if title == reference_name:
# Set time distinction to zero for all lapsfor lap, row in matches.iterrows():matches.loc[lap,f’seconds_to_reference_{reference_name}’] = 0
# If racer shouldn’t be the reference:if title != reference_name:
# a. For every lap discover the closest level in racer’s knowledge primarily based on lat, lon.for lap, row in matches.iterrows():
# Step 1: set the place and lap distance from the referencetarget_coordinates = matches.loc[lap][[‘latitude’, ‘longitude’]].valuestarget_distance = matches.loc[lap][‘distance_cum’]
# Step 2: discover the datapoint that might be within the centre of the windowfirst_crossing_index = (df_dict[name][‘distance_cum’] > target_distance).idxmax()
# Step 3: choose the 20 candidate datapoints to search for the matchwindow_size = 20window_sample = df_dict[name].loc[first_crossing_index-(window_size//2):first_crossing_index+(window_size//2)]candidates = window_sample[[‘latitude’, ‘longitude’]].values
# Step 4: get the closest match utilizing the coordinatesnn = NearestNeighbors(n_neighbors=1, metric=’euclidean’)nn.match(candidates)distance, indice = nn.kneighbors([target_coordinates])
nearest_timestamp = window_sample.iloc[indice.flatten()][‘date_time’].valuesnearest_distance_cum = window_sample.iloc[indice.flatten()][‘distance_cum’].valueseuclidean_distance = distance
matches.loc[lap,f’nearest_timestamp_{name}’] = nearest_timestamp[0]matches.loc[lap,f’nearest_distance_cum_{name}’] = nearest_distance_cum[0]matches.loc[lap,f’euclidean_distance_{name}’] = euclidean_distance
# b. Calculate time distinction between racer and reference at this pointmatches[f’time_to_ref_{name}’] = matches[f’nearest_timestamp_{name}’] – matches[‘date_time’]
# c. Retailer time distinction and different related datamatches[f’time_to_ref_diff_{name}’] = matches[f’time_to_ref_{name}’].diff()matches[f’time_to_ref_diff_{name}’] = matches[f’time_to_ref_diff_{name}’].fillna(pd.Timedelta(seconds=0))
# d. Format knowledge utilizing helper functionsmatches[f’lap_difference_seconds_{name}’] = matches[f’time_to_ref_diff_{name}’].apply(get_seconds)matches[f’lap_difference_formatted_{name}’] = matches[f’time_to_ref_diff_{name}’].apply(format_timedelta)
matches[f’seconds_to_reference_{name}’] = matches[f’time_to_ref_{name}’].apply(get_seconds)matches[f’time_to_reference_formatted_{name}’] = matches[f’time_to_ref_{name}’].apply(format_timedelta)
#### 3. Return processed knowledge with time differencesreturn matches
Beneath the code to implement the logic and retailer outcomes on the dataframe matches_gap_to_reference:
# Lap distancelap_distance = 1000
# Retailer the DataFrames in a dictionarydf_dict = {‘jimena’: jimena_df,’juan’: juan_df,’pedro’: pedro_df,}
# Retailer the Lap DataFrames in a dictionarylaps_dict = {‘jimena’: interpolate_laps(jimena_df , lap_distance),’juan’: interpolate_laps(juan_df , lap_distance),’pedro’: interpolate_laps(pedro_df , lap_distance)}
# Calculate gaps to referencereference_name = ‘juan’matches_gap_to_reference = gap_to_reference(laps_dict, df_dict, reference_name)
The columns of the ensuing dataframe comprise the essential info that might be displayed on the graphs:
[ad_2]
Source link


