1. 数据来源及数据背景
数据来源: https://www.kaggle.com/c/bike-sharing-demand/data, 数据有训练集和测试集, 在训练集中包含10886个样本以及12个字段, 通过训练集上自行车租赁数据对自行车租赁需求进行预测.
2. 数据概览
1. 读取数据
import pandas as pd
df = pd.read_csv(r'D:\Data\bike.csv')
pd.set_option('display.max_rows',4 )
df
image
通过以上可以得知数据维度10886行X12列, 除了第一列其它均显示为数值, 具体的格式还要进一步查看, 对于各列的解释也放入下一环节.
2. 查看数据整体信息
df.info()
[out]
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10886 entries, 0 to 10885
Data columns (total 12 columns):
datetime 10886 non-null object #时间和日期
season 10886 non-null int64 #季节, 1 =春季,2 =夏季,3 =秋季,4 =冬季
holiday 10886 non-null int64 #是否是假期, 1=是, 0=否
workingday 10886 non-null int64 #是否是工作日, 1=是, 0=否
weather 10886 non-null int64 #天气,1:晴朗,很少有云,部分多云,部分多云; 2:雾+多云,雾+碎云,雾+少云,雾; 3:小雪,小雨+雷雨+散云,小雨+散云; 4:大雨+冰块+雷暴+雾,雪+雾
temp 10886 non-null float64 #温度
atemp 10886 non-null float64 #体感温度
humidity 10886 non-null int64 #相对湿度
windspeed 10886 non-null float64 #风速
casual 10886 non-null int64 #未注册用户租赁数量
registered 10886 non-null int64 #注册用户租赁数量
count 10886 non-null int64 #租赁总数
dtypes: float64(3), int64(8), object(1)
memory usage: 1020.6+ KB
除了datetime为字符串型, 其他均为数值型, 且无缺失值.
3. 描述性统计
df.describe()
image
温度, 体表温度, 相对湿度, 风速均近似对称分布, 而非注册用户, 注册用户,以及总数均右边分布.
4. 偏态和峰态
for i in range(5, 12):
name = df.columns[i]
print('{0}偏态系数为 {1}, 峰态系数为 {2}'.format(name, df[name].skew(), df[name].kurt()))
[out]
temp偏态系数为 0.003690844422472008, 峰态系数为 -0.9145302637630794
atemp偏态系数为 -0.10255951346908665, 峰态系数为 -0.8500756471754651
humidity偏态系数为 -0.08633518364548581, 峰态系数为 -0.7598175375208864
windspeed偏态系数为 0.5887665265853944, 峰态系数为 0.6301328693364932
casual偏态系数为 2.4957483979812567, 峰态系数为 7.551629305632764
registered偏态系数为 1.5248045868182296, 峰态系数为 2.6260809999210672
count偏态系数为 1.2420662117180776, 峰态系数为 1.3000929518398334
temp, atemp, humidity低度偏态, windspeed中度偏态, casual, registered, count高度偏态
temp, atemp, humidity为平峰分布, windspeed,casual, registered, count为尖峰分布.
3.数据预处理
由于没有缺失值, 不用处理缺失值, 看看有没有重复值.
1. 检查重复值
print('未去重: ', df.shape)
print('去重: ', df.drop_duplicates().shape)
[out]
未去重: (10886, 12)
去重: (10886, 12)
没有重复项, 看看异常值.
2. 异常值
通过箱线图查看异常值
import seaborn as sns
import matplotlib.pyplot as plt
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(12, 6))
#绘制箱线图
sns.boxplot(x="windspeed", data=df,ax=axes[0][0])
sns.boxplot(x='casual', data=df, ax=axes[0][1])
sns.boxplot(x='registered', data=df, ax=axes[1][0])
sns.boxplot(x='count', data=df, ax=axes[1][1])
plt.show()
image
租赁数量会受小时的影响, 比如说上班高峰期等, 故在这里先不处理异常值.
3. 数据加工
转换"时间和日期"的格式, 并提取出小时, 日, 月, 年.
#转换格式, 并提取出小时, 星期几, 月份
df['datetime'] = pd.to_datetime(df['datetime'])
df['hour'] = df.datetime.dt.hour
df['week'] = df.datetime.dt.dayofweek
df['month'] = df.datetime.dt.month
df['year_month'] = df.datetime.dt.strftime('%Y-%m')
df['date'] = df.datetime.dt.date
#删除datetime
df.drop('datetime', axis = 1, inplace = True)
df
image
4. 特征分析
1. 日期和租赁数量
import matplotlib
#设置中文字体
font = {'family': 'SimHei'}
matplotlib.rc('font', **font)
#分别计算日期和月份中位数
group_date = df.groupby('date')['count'].median()
group_month = df.groupby('year_month')['count'].median()
group_month.index = pd.to_datetime(group_month.index)
plt.figure(figsize=(16,5))
plt.plot(group_date.index, group_date.values, '-', color = 'b', label = '每天租赁数量中位数', alpha=0.8)
plt.plot(group_month.index, group_month.values, '-o', color='orange', label = '每月租赁数量中位数')
plt.legend()
plt.show()
2012年相比2011年租赁数量有所增长, 且波动幅度相类似.
image2. 月份和租赁数量
import seaborn as sns
plt.figure(figsize=(10, 4))
sns.boxplot(x='month', y='count', data=df)
plt.show()
与上图的波动幅度基本一致, 另外每个月均有不同程度的离群值.
image
3. 季节和租赁数量
plt.figure(figsize=(8, 4))
sns.boxplot(x='season', y='count', data=df)
plt.show()
就中位数来说, 秋季是最多的, 春季最少且离群值较多.
image4. 星期和租赁数量
fig, axes = plt.subplots(nrows=3, ncols=1, figsize=(12, 8))
sns.boxplot(x="week",y='casual' ,data=df,ax=axes[0])
sns.boxplot(x='week',y='registered', data=df, ax=axes[1])
sns.boxplot(x='week',y='count', data=df, ax=axes[2])
plt.show()
就中位数来说, 未注册用户周六和周日较多, 而注册用户则周内较多, 对应的总数也是周内较多, 且周内在总数的离群值较多(0代表周一, 6代表周日)
image5. 节假日, 工作日和租赁数量
fig, axes = plt.subplots(nrows=3, ncols=2, figsize=(9, 7))
sns.boxplot(x='holiday', y='casual', data=df, ax=axes[0][0])
sns.boxplot(x='holiday', y='registered', data=df, ax=axes[1][0])
sns.boxplot(x='holiday', y='count', data=df, ax=axes[2][0])
sns.boxplot(x='workingday', y='casual', data=df, ax=axes[0][1])
sns.boxplot(x='workingday', y='registered', data=df, ax=axes[1][1])
sns.boxplot(x='workingday', y='count', data=df, ax=axes[2][1])
plt.show()
未注册用户: 在节假日较多, 在工作日较少
注册用户: 在节假日较少, 在工作日较多
总的来说, 节假日租赁较少, 工作日租赁较多, 初步猜测多数未注册用户租赁自行车是用来非工作日出游, 而多数注册用户则是工作日用来上班或者上学.
image6. 小时和租赁数量
#绘制第一个子图
plt.figure(1, figsize=(14, 8))
plt.subplot(221)
hour_casual = df[df.holiday==1].groupby('hour')['casual'].median()
hour_registered = df[df.holiday==1].groupby('hour')['registered'].median()
hour_count = df[df.holiday==1].groupby('hour')['count'].median()
plt.plot(hour_casual.index, hour_casual.values, '-', color='r', label='未注册用户')
plt.plot(hour_registered.index, hour_registered.values, '-', color='g', label='注册用户')
plt.plot(hour_count.index, hour_count.values, '-o', color='c', label='所有用户')
plt.legend()
plt.xticks(hour_casual.index)
plt.title('未注册用户和注册用户在节假日自行车租赁情况')
#绘制第二个子图
plt.subplot(222)
hour_casual = df[df.workingday==1].groupby('hour')['casual'].median()
hour_registered = df[df.workingday==1].groupby('hour')['registered'].median()
hour_count = df[df.workingday==1].groupby('hour')['count'].median()
plt.plot(hour_casual.index, hour_casual.values, '-', color='r', label='未注册用户')
plt.plot(hour_registered.index, hour_registered.values, '-', color='g', label='注册用户')
plt.plot(hour_count.index, hour_count.values, '-o', color='c', label='所有用户')
plt.legend()
plt.title('未注册用户和注册用户在工作日自行车租赁情况')
plt.xticks(hour_casual.index)
#绘制第三个子图
plt.subplot(212)
hour_casual = df.groupby('hour')['casual'].median()
hour_registered = df.groupby('hour')['registered'].median()
hour_count = df.groupby('hour')['count'].median()
plt.plot(hour_casual.index, hour_casual.values, '-', color='r', label='未注册用户')
plt.plot(hour_registered.index, hour_registered.values, '-', color='g', label='注册用户')
plt.plot(hour_count.index, hour_count.values, '-o', color='c', label='所有用户')
plt.legend()
plt.title('未注册用户和注册用户自行车租赁情况')
plt.xticks(hour_casual.index)
plt.show()
在节假日, 未注册用户和注册用户走势相接近, 不过未注册用户最高峰在14点, 而注册用户则是17点
在工作日, 注册用户呈现出双峰走势, 在8点和17点均为用车高峰期, 而这正是上下班或者上下学高峰期.
对于注册用户来说, 17点在节假日和工作日均为高峰期, 说明部分用户在节假日可能未必休假.
image- 天气和总租赁数量
fig, ax = plt.subplots(3, 1, figsize=(12, 6))
sns.boxplot(x='weather', y='casual', hue='workingday',data=df, ax=ax[0])
sns.boxplot(x='weather', y='registered',hue='workingday', data=df, ax=ax[1])
sns.boxplot(x='weather', y='count',hue='workingday', data=df, ax=ax[2])
就中位数而言未注册用户和注册用户均表现为: 在工作日和非工作日租赁数量均随着天气的恶劣而减少, 特别地, 当天气为大雨大雪天(4)且非工作日均没有自行车租赁.
image从图上可以看出, 大雨大雪天只有一个数据, 我们看看原数据.
df[df.weather==4]
只有在2012年1月9日18时为大雨大雪天, 说明天气是突然变化的, 部分用户可能因为没有看天气预报而租赁自行车, 当然也有其他原因.
image另外, 发现1月份是春季, 看看它的季节划分规则.
sns.boxplot(x='season', y='month',data=df)
123为春季, 456为夏季, 789为秋季...
image季节的划分通常和纬度相关, 而这份数据是用来预测美国华盛顿的租赁数量, 且美国和我国的纬度基本一样, 故按照345春节, 678夏季..这个规则来重新划分.
import numpy as np
df['group_season'] = np.where((df.month <=5) & (df.month >=3), 1,
np.where((df.month <=8) & (df.month >=6), 2,
np.where((df.month <=11) & (df.month >=9), 3, 4)))
fig, ax = plt.subplots(2, 1, figsize=(12, 6))
#绘制气温和季节箱线图
sns.boxplot(x='season', y='temp',data=df, ax=ax[0])
sns.boxplot(x='group_season', y='temp',data=df, ax=ax[1])
第一个图是调整之前的, 就中位数来说, 春季气温最低, 秋季气温最高
第二个图是调整之后的, 就中位数来说, 冬季气温最低, 夏季气温最高
image显然第二张的图的结果较符合常理, 故删除另外那一列.
df.drop('season', axis=1, inplace=True)
df.shape
[out]
(10886, 16)
- 其他变量和总租赁数量的关系
这里我直接使用利用seaborn的pairplot绘制剩余的温度, 体感温度, 相对湿度, 风速这四个连续变量与未注册用户和注册用户的关系在一张图上.
sns.pairplot(df[['temp', 'atemp', 'humidity', 'windspeed', 'casual', 'registered', 'count']])
为了方便纵览全局, 我将图片尺寸缩小, 如下图所示. 纵轴从上往下依次是温度, 体感温度, 相对湿度, 风速, 未注册用户, 注册用户, 所有用户, 横轴从左往右是同样的顺序.
image从图上可以看出, 温度和体感温度分别与未注册用户, 注册用户, 所有用户均有一定程度的正相关, 而相对湿度和风速与之呈现一定程度的负相关. 另外, 其他变量之间也有不同程度的相关关系.
另外, 第四列(风速)在散点图中间有明显的间隙. 需要揪出这一块来看看.
df['windspeed']
[out]
0 0.0000
1 0.0000
2 0.0000
...
10883 15.0013
10884 6.0032
10885 8.9981
Name: windspeed, Length: 10886, dtype: float64
风速为0, 这明显不合理, 把其当成缺失值来处理. 我这里选择的是向后填充.
df.loc[df.windspeed == 0, 'windspeed'] = np.nan
df.fillna(method='bfill', inplace=True)
到这里特征分析就完成了, 剩余的工作会尽快补上...
网友评论