rowingdata 3.6.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rowingdata/__init__.py +2 -0
- rowingdata/__main__.py +2 -0
- rowingdata/boatedit.py +15 -0
- rowingdata/checkdatafiles.py +216 -0
- rowingdata/copystats.py +22 -0
- rowingdata/crewnerdplot.py +37 -0
- rowingdata/crewnerdplottime.py +37 -0
- rowingdata/csvparsers.py +3114 -0
- rowingdata/ergdataplot.py +31 -0
- rowingdata/ergdataplottime.py +31 -0
- rowingdata/ergdatatotcx.py +32 -0
- rowingdata/ergstickplot.py +31 -0
- rowingdata/ergstickplottime.py +32 -0
- rowingdata/ergsticktotcx.py +32 -0
- rowingdata/example.csv +5171 -0
- rowingdata/gpxtools.py +70 -0
- rowingdata/gpxwrite.py +151 -0
- rowingdata/konkatenaadje.py +19 -0
- rowingdata/laptesting.py +293 -0
- rowingdata/obsolete.py +654 -0
- rowingdata/otherparsers.py +718 -0
- rowingdata/painsled_desktop_plot.py +30 -0
- rowingdata/painsled_desktop_plottime.py +29 -0
- rowingdata/painsled_desktop_toc2.py +30 -0
- rowingdata/painsledplot.py +27 -0
- rowingdata/painsledplottime.py +27 -0
- rowingdata/painsledtoc2.py +23 -0
- rowingdata/roweredit.py +15 -0
- rowingdata/rowingdata.py +6941 -0
- rowingdata/rowproplot.py +31 -0
- rowingdata/rowproplottime.py +31 -0
- rowingdata/speedcoachplot.py +31 -0
- rowingdata/speedcoachplottime.py +31 -0
- rowingdata/speedcoachtoc2.py +36 -0
- rowingdata/tcxplot.py +38 -0
- rowingdata/tcxplot_nogeo.py +38 -0
- rowingdata/tcxplottime.py +33 -0
- rowingdata/tcxplottime_nogeo.py +33 -0
- rowingdata/tcxtoc2.py +30 -0
- rowingdata/tcxtools.py +417 -0
- rowingdata/trainingparser.py +302 -0
- rowingdata/utils.py +135 -0
- rowingdata/windcorrected.py +48 -0
- rowingdata/writetcx.py +312 -0
- rowingdata-3.6.8.dist-info/LICENSE +21 -0
- rowingdata-3.6.8.dist-info/METADATA +1149 -0
- rowingdata-3.6.8.dist-info/RECORD +49 -0
- rowingdata-3.6.8.dist-info/WHEEL +5 -0
- rowingdata-3.6.8.dist-info/top_level.txt +1 -0
rowingdata/gpxtools.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# pylint: disable=C0103
|
|
2
|
+
from __future__ import absolute_import
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import re
|
|
5
|
+
import sys
|
|
6
|
+
import unicodedata
|
|
7
|
+
import codecs
|
|
8
|
+
import xmltodict as xd
|
|
9
|
+
from dateutil import parser
|
|
10
|
+
import arrow
|
|
11
|
+
import gzip
|
|
12
|
+
import numpy as np
|
|
13
|
+
import string
|
|
14
|
+
from six import unichr
|
|
15
|
+
|
|
16
|
+
from lxml import etree
|
|
17
|
+
from docopt import docopt
|
|
18
|
+
|
|
19
|
+
ns1 = 'http://www.topografix.com/GPX/1/1'
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def gpxtodf2(path):
|
|
24
|
+
tree = etree.parse(path)
|
|
25
|
+
root = tree.getroot()
|
|
26
|
+
|
|
27
|
+
tracks = []
|
|
28
|
+
lapnr = 0
|
|
29
|
+
|
|
30
|
+
for element in root.iter():
|
|
31
|
+
if element.tag == '{%s}trkseg'%ns1:
|
|
32
|
+
lapnr += 1
|
|
33
|
+
if element.tag == '{%s}trkpt'%ns1:
|
|
34
|
+
tracks.append((lapnr,element))
|
|
35
|
+
|
|
36
|
+
t = []
|
|
37
|
+
d = []
|
|
38
|
+
hr = []
|
|
39
|
+
power = []
|
|
40
|
+
lat = []
|
|
41
|
+
cadence = []
|
|
42
|
+
lon = []
|
|
43
|
+
lapid = []
|
|
44
|
+
|
|
45
|
+
for lapnr,element in tracks:
|
|
46
|
+
latitude, longitude = element.values()
|
|
47
|
+
lapid.append(lapnr)
|
|
48
|
+
lat.append(float(latitude))
|
|
49
|
+
lon.append(float(longitude))
|
|
50
|
+
for child in element:
|
|
51
|
+
if child.tag == '{%s}time'%ns1:
|
|
52
|
+
try:
|
|
53
|
+
time = parser.parse(child.text)
|
|
54
|
+
timestamp = arrow.get(time).timestamp+arrow.get(time).microsecond/1.e6
|
|
55
|
+
t.append(timestamp)
|
|
56
|
+
except KeyError:
|
|
57
|
+
t.append(np.nan)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
df = pd.DataFrame(
|
|
61
|
+
{
|
|
62
|
+
'TimeStamp (sec)':t,
|
|
63
|
+
' latitude':lat,
|
|
64
|
+
' longitude':lon,
|
|
65
|
+
' lapIdx':lapid,
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
return df
|
rowingdata/gpxwrite.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from __future__ import absolute_import
|
|
2
|
+
from __future__ import print_function
|
|
3
|
+
import time
|
|
4
|
+
import datetime
|
|
5
|
+
from dateutil import parser as ps
|
|
6
|
+
import lxml
|
|
7
|
+
import arrow
|
|
8
|
+
import numpy as np
|
|
9
|
+
from lxml import etree, objectify
|
|
10
|
+
from lxml.etree import XMLSyntaxError
|
|
11
|
+
import six.moves.urllib.request, six.moves.urllib.error, six.moves.urllib.parse
|
|
12
|
+
from six.moves import range
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
if sys.version_info[0]<=2:
|
|
16
|
+
pythonversion = 2
|
|
17
|
+
textwritemode = 'w'
|
|
18
|
+
else:
|
|
19
|
+
pythonversion = 3
|
|
20
|
+
textwritemode = 'wt'
|
|
21
|
+
from io import open
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
namespace='http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2'
|
|
25
|
+
|
|
26
|
+
empty_gpx = """
|
|
27
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no" ?><gpx xmlns="http://www.topografix.com/GPX/1/1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" creator="Oregon 400t" version="1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd"><metadata><link href="http://www.garmin.com"><text>Garmin International</text></link><time>2018-03-17T12:59:13</time></metadata><trk><name>Export by rowingdata</name>
|
|
28
|
+
</trk></gpx>
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def lap_begin(f,datetimestring,totalmeters,avghr,maxhr,avgspm,totalseconds):
|
|
32
|
+
f.write('<?xml version="1.0" encoding="UTF-8" standalone="no" ?>')
|
|
33
|
+
f.write('<gpx xmlns="http://www.topografix.com/GPX/1/1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" creator="Oregon 400t" version="1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd"><metadata><link href="http://www.garmin.com"><text>Garmin International</text></link>')
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def write_gpx(gpxFile,df,row_date="2016-01-01",notes="Exported by rowingdata"):
|
|
37
|
+
if notes==None:
|
|
38
|
+
notes="Exported by rowingdata"
|
|
39
|
+
f=open(gpxFile,'w')
|
|
40
|
+
|
|
41
|
+
totalseconds=int(df['TimeStamp (sec)'].max()-df['TimeStamp (sec)'].min())
|
|
42
|
+
totalmeters=int(df['cum_dist'].max())
|
|
43
|
+
avghr=int(df[' HRCur (bpm)'].mean())
|
|
44
|
+
if avghr == 0:
|
|
45
|
+
avghr=1
|
|
46
|
+
maxhr=int(df[' HRCur (bpm)'].max())
|
|
47
|
+
if maxhr == 0:
|
|
48
|
+
maxhr=1
|
|
49
|
+
avgspm=int(df[' Cadence (stokes/min)'].mean())
|
|
50
|
+
|
|
51
|
+
seconds=df['TimeStamp (sec)'].values
|
|
52
|
+
distancemeters=df['cum_dist'].values
|
|
53
|
+
heartrate=df[' HRCur (bpm)'].values.astype(int)
|
|
54
|
+
cadence=np.round(df[' Cadence (stokes/min)'].values).astype(int)
|
|
55
|
+
|
|
56
|
+
nr_rows=len(seconds)
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
lat=df[' latitude'].values
|
|
60
|
+
except KeyError:
|
|
61
|
+
lat=np.zeros(nr_rows)
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
lon=df[' longitude'].values
|
|
65
|
+
except KeyError:
|
|
66
|
+
lon=np.zeros(nr_rows)
|
|
67
|
+
|
|
68
|
+
haspower=1
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
power=df[' Power (watts)'].values
|
|
72
|
+
except KeyError:
|
|
73
|
+
haspower=0
|
|
74
|
+
|
|
75
|
+
s="2000-01-01"
|
|
76
|
+
tt=ps.parse(s)
|
|
77
|
+
#timezero=time.mktime(tt.timetuple())
|
|
78
|
+
timezero = arrow.get(tt).timestamp()
|
|
79
|
+
if seconds[0]<timezero:
|
|
80
|
+
# print("Taking Row_Date ",row_date)
|
|
81
|
+
dateobj=ps.parse(row_date)
|
|
82
|
+
#unixtimes=seconds+time.mktime(dateobj.timetuple())
|
|
83
|
+
unixtimes=seconds+arrow.get(dateobj).timestamp()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
datetimestring=row_date
|
|
88
|
+
|
|
89
|
+
lap_begin(f,datetimestring,totalmeters,avghr,maxhr,avgspm,totalseconds)
|
|
90
|
+
|
|
91
|
+
ts = datetime.datetime.fromtimestamp(unixtimes[0]).isoformat()
|
|
92
|
+
s = '<time>{ts}</time></metadata><trk><name>Export by rowingdata</name><trkseg>'.format(
|
|
93
|
+
ts=ts,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
f.write(s)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
for i in range(nr_rows):
|
|
100
|
+
s = ' <trkpt lat="{lat}" lon="{lon}">\n'.format(
|
|
101
|
+
lat=lat[i],
|
|
102
|
+
lon=lon[i]
|
|
103
|
+
)
|
|
104
|
+
f.write(s)
|
|
105
|
+
#s=datetime.datetime.fromtimestamp(unixtimes[i]).isoformat()
|
|
106
|
+
s = arrow.get(unixtimes[i]).isoformat()
|
|
107
|
+
f.write(' <time>{s}</time>\n'.format(s=s))
|
|
108
|
+
f.write(' </trkpt>\n')
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
f.write('</trkseg>')
|
|
113
|
+
f.write('</trk>')
|
|
114
|
+
f.write('</gpx>')
|
|
115
|
+
|
|
116
|
+
f.close()
|
|
117
|
+
|
|
118
|
+
file=open(gpxFile,'r')
|
|
119
|
+
|
|
120
|
+
some_xml_string=file.read()
|
|
121
|
+
|
|
122
|
+
file.close()
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
xsd_file=six.moves.urllib.request.urlopen("http://www.topografix.com/GPX/1/1/gpx.xsd")
|
|
126
|
+
output=open('gpx.xsd','w')
|
|
127
|
+
if pythonversion <= 2:
|
|
128
|
+
output.write(xsd_file.read().replace('\n',''))
|
|
129
|
+
else:
|
|
130
|
+
output.write(xsd_file.read().decode('utf-8').replace('\n',''))
|
|
131
|
+
output.close()
|
|
132
|
+
xsd_filename="gpx.xsd"
|
|
133
|
+
|
|
134
|
+
# Run some tests
|
|
135
|
+
try:
|
|
136
|
+
tree=objectify.parse(gpxFile)
|
|
137
|
+
try:
|
|
138
|
+
schema=etree.XMLSchema(file=xsd_filename)
|
|
139
|
+
parser=objectify.makeparser(schema=schema)
|
|
140
|
+
objectify.fromstring(some_xml_string, parser)
|
|
141
|
+
except XMLSyntaxError:
|
|
142
|
+
pass
|
|
143
|
+
except:
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
except six.moves.urllib.error.URLError:
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
return 1
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import absolute_import
|
|
2
|
+
from __future__ import print_function
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
x1=np.array([1,3,4])
|
|
7
|
+
y1=x1*2
|
|
8
|
+
x2=np.array([1,2,3,4,5,6,7])
|
|
9
|
+
y2=x2*2-1
|
|
10
|
+
|
|
11
|
+
def test():
|
|
12
|
+
x=np.concatenate((x1,x2))
|
|
13
|
+
y=np.concatenate((y1,y2))
|
|
14
|
+
print((len(x),len(y)))
|
|
15
|
+
data=pd.DataFrame({'x':x,
|
|
16
|
+
'y':y})
|
|
17
|
+
data=data.drop_duplicates(subset='x').sort('x',ascending=1)
|
|
18
|
+
|
|
19
|
+
return data
|
rowingdata/laptesting.py
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
def summarize_rowing_data(df):
|
|
4
|
+
# Ensure the DataFrame is sorted by time
|
|
5
|
+
df = df.sort_values(by='TimeStamp (sec)')
|
|
6
|
+
|
|
7
|
+
# Convert numeric columns to numeric type, handling non-numeric values
|
|
8
|
+
numeric_cols = [
|
|
9
|
+
'TimeStamp (sec)',
|
|
10
|
+
' Horizontal (meters)',
|
|
11
|
+
' Cadence (stokes/min)',
|
|
12
|
+
' HRCur (bpm)',
|
|
13
|
+
' Stroke500mPace (sec/500m)',
|
|
14
|
+
' Power (watts)',
|
|
15
|
+
' DriveLength (meters)',
|
|
16
|
+
' StrokeDistance (meters)',
|
|
17
|
+
' DriveTime (ms)',
|
|
18
|
+
' StrokeRecoveryTime (ms)',
|
|
19
|
+
' AverageDriveForce (lbs)',
|
|
20
|
+
' PeakDriveForce (lbs)',
|
|
21
|
+
' lapIdx',
|
|
22
|
+
' ElapsedTime (sec)',
|
|
23
|
+
' WorkoutState'
|
|
24
|
+
]
|
|
25
|
+
df[numeric_cols] = df[numeric_cols].apply(pd.to_numeric, errors='coerce')
|
|
26
|
+
|
|
27
|
+
# Drop rows with NaN values
|
|
28
|
+
df = df.dropna()
|
|
29
|
+
|
|
30
|
+
# Initialize variables to store summarized data
|
|
31
|
+
lap_summaries = []
|
|
32
|
+
|
|
33
|
+
# Initialize variables to track the start time and data for each lap
|
|
34
|
+
lap_start_time = None
|
|
35
|
+
lap_end_time = None
|
|
36
|
+
lap_cadences = []
|
|
37
|
+
lap_paces = [] # Added lap_paces to store paces for each lap
|
|
38
|
+
lap_distances = []
|
|
39
|
+
lap_powers = []
|
|
40
|
+
lap_heart_rates = []
|
|
41
|
+
lap_id = None
|
|
42
|
+
|
|
43
|
+
# Use a mask to filter rows where the workout_state is work strokes
|
|
44
|
+
work_strokes_mask = df[' WorkoutState'].isin([1, 4, 5, 6, 7, 8, 9])
|
|
45
|
+
|
|
46
|
+
# Iterate through rows in the DataFrame
|
|
47
|
+
for index, row in df.iterrows():
|
|
48
|
+
if work_strokes_mask[index]:
|
|
49
|
+
# If the lap has started or the lap ID changes, append cadence, pace, and heart rate to lap lists
|
|
50
|
+
if lap_start_time is not None and row[' lapIdx'] == lap_id:
|
|
51
|
+
lap_cadences.append(row[' Cadence (stokes/min)'])
|
|
52
|
+
lap_paces.append(row[' Stroke500mPace (sec/500m)'])
|
|
53
|
+
lap_distances.append(row[' StrokeDistance (meters)'])
|
|
54
|
+
lap_powers.append(row[' Power (watts)'])
|
|
55
|
+
lap_heart_rates.append(row[' HRCur (bpm)'])
|
|
56
|
+
lap_end_time = row['TimeStamp (sec)']
|
|
57
|
+
else:
|
|
58
|
+
# If the lap has not started or the lap ID changes, initialize lap-related variables
|
|
59
|
+
if lap_start_time is not None:
|
|
60
|
+
# If a lap has ended, calculate lap duration and append averages to summary list
|
|
61
|
+
lap_duration = lap_end_time - lap_start_time
|
|
62
|
+
avg_cadence = sum(lap_cadences) / len(lap_cadences)
|
|
63
|
+
avg_pace = sum(lap_paces) / len(lap_paces)
|
|
64
|
+
avg_speed = 500 / avg_pace # Speed is derived from the reciprocal of pace
|
|
65
|
+
total_distance_per_lap = sum(lap_distances) # Improved calculation of total distance per lap
|
|
66
|
+
avg_power = sum(lap_powers) / len(lap_powers)
|
|
67
|
+
max_heart_rate = max(lap_heart_rates)
|
|
68
|
+
avg_heart_rate = sum(lap_heart_rates) / len(lap_heart_rates)
|
|
69
|
+
total_strokes_in_lap = len(lap_cadences)
|
|
70
|
+
distance_per_stroke = total_distance_per_lap / total_strokes_in_lap
|
|
71
|
+
|
|
72
|
+
lap_summaries.append({
|
|
73
|
+
'lap_id': lap_id,
|
|
74
|
+
'lap_duration': lap_duration,
|
|
75
|
+
'avg_cadence': avg_cadence,
|
|
76
|
+
'avg_pace': avg_pace,
|
|
77
|
+
'avg_speed': avg_speed,
|
|
78
|
+
'total_distance_per_lap': total_distance_per_lap,
|
|
79
|
+
'avg_power': avg_power,
|
|
80
|
+
'max_heart_rate': max_heart_rate,
|
|
81
|
+
'avg_heart_rate': avg_heart_rate,
|
|
82
|
+
'total_strokes_in_lap': total_strokes_in_lap,
|
|
83
|
+
'distance_per_stroke': distance_per_stroke
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
# Reset lap-related variables
|
|
87
|
+
lap_start_time = row['TimeStamp (sec)']
|
|
88
|
+
lap_end_time = row['TimeStamp (sec)']
|
|
89
|
+
lap_cadences = [row[' Cadence (stokes/min)']]
|
|
90
|
+
lap_paces = [row[' Stroke500mPace (sec/500m)']]
|
|
91
|
+
lap_distances = [row[' StrokeDistance (meters)']]
|
|
92
|
+
lap_powers = [row[' Power (watts)']]
|
|
93
|
+
lap_heart_rates = [row[' HRCur (bpm)']]
|
|
94
|
+
lap_id = row[' lapIdx']
|
|
95
|
+
|
|
96
|
+
# If the last lap is work stroke, add it to the summary
|
|
97
|
+
if lap_start_time is not None:
|
|
98
|
+
lap_duration = lap_end_time - lap_start_time
|
|
99
|
+
avg_cadence = sum(lap_cadences) / len(lap_cadences)
|
|
100
|
+
avg_pace = sum(lap_paces) / len(lap_paces)
|
|
101
|
+
avg_speed = 500 / avg_pace # Speed is derived from the reciprocal of pace
|
|
102
|
+
total_distance_per_lap = sum(lap_distances) # Improved calculation of total distance per lap
|
|
103
|
+
avg_power = sum(lap_powers) / len(lap_powers)
|
|
104
|
+
max_heart_rate = max(lap_heart_rates)
|
|
105
|
+
avg_heart_rate = sum(lap_heart_rates) / len(lap_heart_rates)
|
|
106
|
+
total_strokes_in_lap = len(lap_cadences)
|
|
107
|
+
distance_per_stroke = total_distance_per_lap / total_strokes_in_lap
|
|
108
|
+
|
|
109
|
+
lap_summaries.append({
|
|
110
|
+
'lap_id': lap_id,
|
|
111
|
+
'lap_duration': lap_duration,
|
|
112
|
+
'avg_cadence': avg_cadence,
|
|
113
|
+
'avg_pace': avg_pace,
|
|
114
|
+
'avg_speed': avg_speed,
|
|
115
|
+
'total_distance_per_lap': total_distance_per_lap,
|
|
116
|
+
'avg_power': avg_power,
|
|
117
|
+
'max_heart_rate': max_heart_rate,
|
|
118
|
+
'avg_heart_rate': avg_heart_rate,
|
|
119
|
+
'total_strokes_in_lap': total_strokes_in_lap,
|
|
120
|
+
'distance_per_stroke': distance_per_stroke
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
# Create a new DataFrame with the summarized data
|
|
124
|
+
summary_df = pd.DataFrame(lap_summaries)
|
|
125
|
+
|
|
126
|
+
return summary_df
|
|
127
|
+
|
|
128
|
+
import pandas as pd
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
import pandas as pd
|
|
132
|
+
|
|
133
|
+
def summarize_entire_workout(df):
|
|
134
|
+
# Ensure the DataFrame is sorted by time
|
|
135
|
+
df = df.sort_values(by='TimeStamp (sec)')
|
|
136
|
+
|
|
137
|
+
# Convert numeric columns to numeric type, handling non-numeric values
|
|
138
|
+
numeric_cols = [
|
|
139
|
+
'TimeStamp (sec)',
|
|
140
|
+
' Horizontal (meters)',
|
|
141
|
+
' Cadence (stokes/min)',
|
|
142
|
+
' HRCur (bpm)',
|
|
143
|
+
' Stroke500mPace (sec/500m)',
|
|
144
|
+
' Power (watts)',
|
|
145
|
+
' DriveLength (meters)',
|
|
146
|
+
' StrokeDistance (meters)',
|
|
147
|
+
' DriveTime (ms)',
|
|
148
|
+
' StrokeRecoveryTime (ms)',
|
|
149
|
+
' AverageDriveForce (lbs)',
|
|
150
|
+
' PeakDriveForce (lbs)',
|
|
151
|
+
' lapIdx',
|
|
152
|
+
' ElapsedTime (sec)',
|
|
153
|
+
' WorkoutState'
|
|
154
|
+
]
|
|
155
|
+
df[numeric_cols] = df[numeric_cols].apply(pd.to_numeric, errors='coerce')
|
|
156
|
+
|
|
157
|
+
# Drop rows with NaN values
|
|
158
|
+
df = df.dropna()
|
|
159
|
+
|
|
160
|
+
# Use a mask to filter rows where the workout_state is work strokes or resting strokes
|
|
161
|
+
work_strokes_mask = df[' WorkoutState'].isin([1, 4, 5, 6, 7, 8, 9])
|
|
162
|
+
rest_strokes_mask = df[' WorkoutState'] == 3
|
|
163
|
+
|
|
164
|
+
# Filter rows for work strokes, resting strokes, and entire workout
|
|
165
|
+
work_intervals_df = df[work_strokes_mask]
|
|
166
|
+
rest_intervals_df = df[rest_strokes_mask]
|
|
167
|
+
|
|
168
|
+
# Calculate total distance for work intervals and resting intervals
|
|
169
|
+
total_distance_work_intervals = (
|
|
170
|
+
work_intervals_df.groupby(' lapIdx')[' Horizontal (meters)'].max().diff().fillna(
|
|
171
|
+
work_intervals_df.groupby(' lapIdx')[' Horizontal (meters)'].max()
|
|
172
|
+
).sum()
|
|
173
|
+
)
|
|
174
|
+
total_distance_rest_intervals = (
|
|
175
|
+
rest_intervals_df.groupby(' lapIdx')[' Horizontal (meters)'].max().diff().fillna(
|
|
176
|
+
rest_intervals_df.groupby(' lapIdx')[' Horizontal (meters)'].max()
|
|
177
|
+
).sum()
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Calculate total distance for the entire workout
|
|
181
|
+
total_distance_workout = (
|
|
182
|
+
df.groupby(' lapIdx')[' Horizontal (meters)'].max().diff().fillna(
|
|
183
|
+
df.groupby(' lapIdx')[' Horizontal (meters)'].max()
|
|
184
|
+
).sum()
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# Get the maximum total time among all laps
|
|
188
|
+
total_time = df.groupby(' lapIdx')[' ElapsedTime (sec)'].max().max()
|
|
189
|
+
|
|
190
|
+
# Calculate averages for the entire workout
|
|
191
|
+
avg_cadence_workout = df[' Cadence (stokes/min)'].mean()
|
|
192
|
+
avg_pace_workout = df[' Stroke500mPace (sec/500m)'].mean()
|
|
193
|
+
avg_speed_workout = 500 / avg_pace_workout
|
|
194
|
+
avg_power_workout = df[' Power (watts)'].mean()
|
|
195
|
+
avg_heart_rate_workout = df[' HRCur (bpm)'].mean()
|
|
196
|
+
|
|
197
|
+
# Calculate averages for work intervals
|
|
198
|
+
avg_cadence_work = work_intervals_df[' Cadence (stokes/min)'].mean()
|
|
199
|
+
avg_pace_work = work_intervals_df[' Stroke500mPace (sec/500m)'].mean()
|
|
200
|
+
avg_speed_work = 500 / avg_pace_work
|
|
201
|
+
avg_power_work = work_intervals_df[' Power (watts)'].mean()
|
|
202
|
+
avg_heart_rate_work = work_intervals_df[' HRCur (bpm)'].mean()
|
|
203
|
+
|
|
204
|
+
# Calculate averages for resting intervals
|
|
205
|
+
avg_cadence_rest = rest_intervals_df[' Cadence (stokes/min)'].mean()
|
|
206
|
+
avg_pace_rest = rest_intervals_df[' Stroke500mPace (sec/500m)'].mean()
|
|
207
|
+
avg_speed_rest = 500 / avg_pace_rest
|
|
208
|
+
avg_power_rest = rest_intervals_df[' Power (watts)'].mean()
|
|
209
|
+
avg_heart_rate_rest = rest_intervals_df[' HRCur (bpm)'].mean()
|
|
210
|
+
|
|
211
|
+
# Create a summary dictionary
|
|
212
|
+
summary_dict = {
|
|
213
|
+
'total_distance_workout': total_distance_workout,
|
|
214
|
+
'total_distance_work_intervals': total_distance_work_intervals,
|
|
215
|
+
'total_distance_rest_intervals': total_distance_rest_intervals,
|
|
216
|
+
'total_time': total_time,
|
|
217
|
+
'avg_cadence_workout': avg_cadence_workout,
|
|
218
|
+
'avg_pace_workout': avg_pace_workout,
|
|
219
|
+
'avg_speed_workout': avg_speed_workout,
|
|
220
|
+
'avg_power_workout': avg_power_workout,
|
|
221
|
+
'avg_heart_rate_workout': avg_heart_rate_workout,
|
|
222
|
+
'avg_cadence_work': avg_cadence_work,
|
|
223
|
+
'avg_pace_work': avg_pace_work,
|
|
224
|
+
'avg_speed_work': avg_speed_work,
|
|
225
|
+
'avg_power_work': avg_power_work,
|
|
226
|
+
'avg_heart_rate_work': avg_heart_rate_work,
|
|
227
|
+
'avg_cadence_rest': avg_cadence_rest,
|
|
228
|
+
'avg_pace_rest': avg_pace_rest,
|
|
229
|
+
'avg_speed_rest': avg_speed_rest,
|
|
230
|
+
'avg_power_rest': avg_power_rest,
|
|
231
|
+
'avg_heart_rate_rest': avg_heart_rate_rest
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return summary_dict
|
|
235
|
+
|
|
236
|
+
def create_workout_summary_string(summary_df, summary_dict):
|
|
237
|
+
# Extract values from the summary_dict
|
|
238
|
+
total_distance_workout = int(summary_dict['total_distance_workout'])
|
|
239
|
+
total_distance_work_intervals = int(summary_dict['total_distance_work_intervals'])
|
|
240
|
+
total_distance_rest_intervals = int(summary_dict['total_distance_rest_intervals'])
|
|
241
|
+
total_time = str(summary_dict['total_time'])
|
|
242
|
+
avg_cadence_workout = str(round(summary_dict['avg_cadence_workout'], 1))
|
|
243
|
+
avg_pace_workout = str(summary_dict['avg_pace_workout'])
|
|
244
|
+
avg_speed_workout = str(round(summary_dict['avg_speed_workout'], 1))
|
|
245
|
+
avg_power_workout = str(round(summary_dict['avg_power_workout'], 1))
|
|
246
|
+
avg_heart_rate_workout = str(round(summary_dict['avg_heart_rate_workout'], 1))
|
|
247
|
+
avg_cadence_work = str(round(summary_dict['avg_cadence_work'], 1))
|
|
248
|
+
avg_pace_work = str(summary_dict['avg_pace_work'])
|
|
249
|
+
avg_speed_work = str(round(summary_dict['avg_speed_work'], 1))
|
|
250
|
+
avg_power_work = str(round(summary_dict['avg_power_work'], 1))
|
|
251
|
+
avg_heart_rate_work = str(round(summary_dict['avg_heart_rate_work'], 1))
|
|
252
|
+
avg_cadence_rest = str(round(summary_dict['avg_cadence_rest'], 1))
|
|
253
|
+
avg_pace_rest = str(summary_dict['avg_pace_rest'])
|
|
254
|
+
avg_speed_rest = str(round(summary_dict['avg_speed_rest'], 1))
|
|
255
|
+
avg_power_rest = str(round(summary_dict['avg_power_rest'], 1))
|
|
256
|
+
avg_heart_rate_rest = str(round(summary_dict['avg_heart_rate_rest'], 1))
|
|
257
|
+
|
|
258
|
+
# Create the workout summary string
|
|
259
|
+
workout_summary_string = (
|
|
260
|
+
f"Workout Summary - testdata.csv\n"
|
|
261
|
+
"--|Total|-Total----|--Avg--|-Avg-|Avg-|-Avg-|-Max-|-Avg\n"
|
|
262
|
+
f"--|{total_distance_workout:05d}|{total_time}|{avg_pace_workout}|{avg_power_workout}|"
|
|
263
|
+
f"{avg_cadence_workout}|{avg_heart_rate_workout}|XXX.X|{avg_heart_rate_workout}\n"
|
|
264
|
+
f"W-|{total_distance_work_intervals:05d}|{total_time}|{avg_pace_work}|{avg_power_work}|"
|
|
265
|
+
f"{avg_cadence_work}|{avg_heart_rate_work}|XXX.X|{avg_heart_rate_work}\n"
|
|
266
|
+
f"R-|{total_distance_rest_intervals:05d}|00:00.0|00:00.0|000.0|00.0|000.0|{avg_heart_rate_rest}|00.0\n"
|
|
267
|
+
"Workout Details\n"
|
|
268
|
+
"#-|SDist|-Split-|-SPace-|-Pwr-|SPM-|AvgHR|MaxHR|DPS-\n"
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# Iterate over rows in the summary DataFrame to create details
|
|
272
|
+
for _, row in summary_df.iterrows():
|
|
273
|
+
lap_distance = int(row['total_distance_per_lap'])
|
|
274
|
+
split_time = str(row['lap_duration'])
|
|
275
|
+
split_pace = str(row['avg_pace'])
|
|
276
|
+
split_power = str(row['avg_power'])
|
|
277
|
+
split_cadence = str(row['avg_cadence'])
|
|
278
|
+
avg_heart_rate_split = str(row['avg_heart_rate'])
|
|
279
|
+
max_heart_rate_split = str(row['max_heart_rate'])
|
|
280
|
+
dps = str(row['distance_per_stroke'])
|
|
281
|
+
lap_id = int(row['lap_id'])
|
|
282
|
+
|
|
283
|
+
workout_summary_string += (
|
|
284
|
+
f"{lap_id:02d}|{lap_distance:05d}|{split_time}|{split_pace}|{split_power}|"
|
|
285
|
+
f"{split_cadence}|{avg_heart_rate_split}|{max_heart_rate_split}|{dps}\n"
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
return workout_summary_string
|
|
289
|
+
|
|
290
|
+
# Example usage:
|
|
291
|
+
# Assuming your DataFrame is named 'rowing_data_df'
|
|
292
|
+
#summary_entire_workout = summarize_entire_workout(rowing_data_df)
|
|
293
|
+
#print(summary_entire_workout)
|