马拉松跑步数据

Files & media
date
Sep 23, 2021
slug
marathon
status
Published
tags
python
data
summary
马拉松跑步数据分析及展示
type
Post
URL
 
先引入数据,准备进行分析
# 引入数据
import pandas as pd
data = pd.read_csv('~/data/cbcpv/marathon/marathon.csv')
data.sample(5)
OUT:
age
gender
split
final
19841
34
M
01:55:25
04:50:03
11002
28
W
01:55:00
04:11:00
11619
26
M
01:40:28
04:13:52
4068
34
M
01:38:30
03:30:21
6922
35
M
01:37:44
03:48:37
这个数据集有以下几个特征:
  • age,运动员的年龄
  • gender,运动员的性别
  • split,半程所用时间
  • final,全程所用时间,即最终成绩
自然,要先了解下数据的具体情况
data.info()

# 输出结果
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 37250 entries, 0 to 37249
Data columns (total 4 columns):
 #   Column  Non-Null Count  Dtype
---  ------  --------------  -----
 0   age     37250 non-null  int64
 1   gender  37250 non-null  object
 2   split   37250 non-null  object
 3   final   37250 non-null  object
dtypes: int64(1), object(3)
memory usage: 1.1+ MB
可以看到并没缺失值, 不过splitfinal特征中的数据不实数字类型, 用字符串表示了所用时间长度, 所以我们需要进行转化:
import datetime
def convert_time(s):
    h,m,s=map(int, s.split(':'))
    return datetime.timedelta(hours=h, minutes=m, seconds=s)
使用完成的方法进行数据转换,我们从新读取一下数据:
df=pd.read_csv(
    '~/data/cbcpv/marathon/marathon.csv',
    converters={
        'split': convert_time,
        'final': convert_time
    }
)
df.dtypes

# 输出结果
age                 int64
gender             object
split     timedelta64[ns]
final     timedelta64[ns]
dtype: object
这次数据已经转换为timedelta64类型, 下面我们就要转化时间为整数, 一般的做法都是秒或者毫秒数:
d = datetime.timedelta(hours=1, minutes=0, seconds=0)
df2 = pd.DataFrame({'time':[d]})
df2.astype(int)

# 输出结果
             time
0    3600000000000
我们看到的输出结果,是“纳秒“(ns)单位:
$$1s=10^9ns$$
我们还需要转化为秒:
d=datetime.timedelta(hours=1, minutes=0, seconds=0)
df2=pd.DataFrame({'time':[d]})
df2.astype(int) * 1e-9

# out
       time
0    3600.0
然后我们要讲splitfinal两个特征的数据进行转化
df['split_sec']=df['split'].astype(int) * 1e-9
df['final_sec']=df['final'].astype(int) * 1e-9
df.sample(5)
OUT:
Title
age
gender
split
final
split_sec
final_sec
11725
35
M
0 days 01:53:53
0 days 04:14:19
6833
15259
19815
24
M
0 days 01:58:45
0 days 04:49:57
7125
17397
5754
49
M
0 days 01:42:39
0 days 03:41:05
6159
13265
33166
46
M
0 days 02:31:37
0 days 06:06:17
9097
21977
9226
36
W
0 days 01:49:06
0 days 04:01:55
6546
14515
现在多了两个特征split_secfinal_sec, 都是以秒为单位的浮点数.

描述统计

先了解数据:
df.describe()
OUT:
age
split
final
split_sec
final_sec
37250
37250
37250
37250.000000
37250.000000
40.697369
0 days 02:03:54.425664429
0 days 04:48:09.303597315
7434.425664
17289.303597
10.220043
0 days 00:22:55.093889674
0 days 01:03:32.145345151
1375.093890
3812.145345
17
0 days 01:05:21
0 days 02:08:51
3921.000000
7731.000000
33
0 days 01:48:25
0 days 04:02:24
6505.000000
14544.000000
40
0 days 02:01:13
0 days 04:44:25
7273.000000
17065.000000
48
0 days 02:16:11
0 days 05:27:36
8171.000000
19656.000000
86
0 days 04:59:49
0 days 10:01:08
17989.000000
36068.000000
居然年龄上最大的数据是86, 让我们看看特征的数据分布:
%matplotlib inline
import seaborn as sns
ax=sns.boxplot(x=df['age'])
notion image
这个箱线图反应了, 数据里确实有一些“离群值”.

数据分布

研究下数据分布, 看看split_secfinal_sec
sns.displot(df['split_sec'])
notion image
sns.displot(df['final_sec'])
notion image
整体看来,两个特征下的数据都符合正态分布, 但是final_sec的分布图比较胖.
这次我们把gender这个分类特征添加进来:
sns.violinplot(x='gender', y='final_sec', data=df)
notion image
这些看到, 男性运动员在总体上还是比女性运动员要快一些.

寻找优秀的原因

跑马拉松或者了解这项运动的人都清楚, 运动员很关注整个赛程中前后半程的时间比较,好的选手是后半程用时和前半程近似. 因此, 我们来研究下, 这些运动员前后半程用时情况.
g=sns.jointplot('split_sec', 'final_sec', data=df, kind='hex')

# 绘制一条直线, 作为参考
import numpy as np
g.ax_joint.plot(np.linspace(4000, 16000), np.linspace(8000, 32000), ':k')
notion image
横坐标是splict_sec特征, 即半程用时. 纵轴表示final_sec特征, 全程用时. 途中可以看出, 的确是越优秀的运动员,前半程用时越接近全程用时的一半, 甚至还有少数后半程跑的更快的.
我们做个计算来深入研究下:
df['split_frac']=1-2*df['split_sec']/df['final_sec']
df.sample(5)
OUT:
age
gender
split
final
split_sec
final_sec
split_frac
2065
35
W
0 days 01:31:41
0 days 03:14:40
5501
11680
0.058048
9001
43
W
0 days 01:58:19
0 days 04:00:44
7099
14444
0.017031
30039
34
M
0 days 02:25:17
0 days 05:39:21
8717
20361
0.143755
27456
62
W
0 days 02:13:28
0 days 05:25:01
8008
19501
0.178709
13335
41
M
0 days 01:45:36
0 days 04:21:00
6336
15660
0.190805
用直方图再增加一个参考线来看看split_frac特征中的数据分布:
import matplotlib.pyplot as plt
sns.displot(df['split_frac'], kde=False)
# 垂直于 x 轴的直线,0 表示 x 轴位置
plt.axvline(0, color='k', linestyle='--')
notion image
从这张图中, 更清晰的看到全体参赛者的运动安排.
再来探究下不同特征之间的关系:
sns.pairplot(
    data=df,
    vars=['age','split_sec','final_sec','split_frac'],
    hue='gender'
)
notion image
让我们来看下80岁选手的数量:
(df.age>=80).sum()

# OUT
15
下面, 我们划分下年龄段,看看各年龄段的成绩分布:
df['age_dec']=df['age'].map(lambda age: 10*(age//10))
sns.violinplot(
    x='age_dec',
    y='split_frac',
    hue='gender',
    data=df,
    split=True,
    inner='quartile',
    palette=['lightblue', 'lightpink']
)
notion image
看这张图, 我们发现,不同性别的运动员的split_frac特征数据分布中, 年龄越大,前后端的时间分布比相对集中.
再看看全程用时分布比较:
sns.violinplot(
    x='age_dec',
    y='final_sec',
    hue='gender',
    data=df,
    split=True,
    inner='quartile',
    palette=['lightblue', 'lightpink']
)
notion image
从30岁往后, 明显年纪越大,用时越长.

© Hivan Du 2021 - 2023